diff options
Diffstat (limited to 'Docs')
-rw-r--r-- | Docs/Makefile.am | 12 | ||||
-rwxr-xr-x | Docs/Support/colspec-fix.pl | 78 | ||||
-rwxr-xr-x | Docs/Support/docbook-fixup.pl | 200 | ||||
-rwxr-xr-x | Docs/Support/docbook-prefix.pl | 50 | ||||
-rwxr-xr-x | Docs/Support/docbook-split | 70 | ||||
-rwxr-xr-x | Docs/Support/generate-text-files.pl | 15 | ||||
-rwxr-xr-x | Docs/Support/make-docbook | 29 | ||||
-rwxr-xr-x | Docs/Support/make-makefile | 7 | ||||
-rwxr-xr-x | Docs/Support/test-make-manual | 137 | ||||
-rwxr-xr-x | Docs/Support/test-make-manual-de | 137 | ||||
-rwxr-xr-x | Docs/Support/xwf | 67 | ||||
-rw-r--r-- | Docs/manual.chm | 14 | ||||
-rw-r--r-- | Docs/sp-imp-spec.txt | 1100 | ||||
-rw-r--r-- | Docs/sp-implemented.txt | 112 |
14 files changed, 1249 insertions, 779 deletions
diff --git a/Docs/Makefile.am b/Docs/Makefile.am index ab4288ba9d9..369e02e4654 100644 --- a/Docs/Makefile.am +++ b/Docs/Makefile.am @@ -1,9 +1,8 @@ -# Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB +# Copyright (C) 2000-2006 MySQL AB # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. +# the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,7 +15,7 @@ noinst_SCRIPTS = Support/generate-text-files.pl -EXTRA_DIST = $(noinst_SCRIPTS) mysql.info INSTALL-BINARY +EXTRA_DIST = $(noinst_SCRIPTS) manual.chm mysql.info INSTALL-BINARY TXT_FILES= ../INSTALL-SOURCE ../INSTALL-WIN-SOURCE \ INSTALL-BINARY ../support-files/MacOSX/ReadMe.txt @@ -33,6 +32,11 @@ install-data-hook: $(srcdir)/mysql.info uninstall-local: @RM@ -f $(DESTDIR)$(infodir)/mysql.info +# Problems with "make distclean", works differently for make files +# generated by different versions of the automake. Some require the +# generated files explicitly in DISTCLEANFILES. +DISTCLEANFILES = $(TXT_FILES) + # This target is not used in builds, just for convinience CLEAN_FILES: $(TXT_FILES) touch $(TXT_FILES) diff --git a/Docs/Support/colspec-fix.pl b/Docs/Support/colspec-fix.pl deleted file mode 100755 index 6c64edd1441..00000000000 --- a/Docs/Support/colspec-fix.pl +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/perl -w - -# -# Script to rewrite colspecs from relative values to absolute values -# - -# arjen 2002-03-14 append "cm" specifier to colwidth field. - -use strict; - -my $table_width = 12.75; # Specify the max width of the table in cm -my $gutter_width = 0.55; # Specify the width of the gutters in cm - -my $str = join '', <>; # Push stdin (or file) - -$str =~ s{([\t ]*(<colspec colwidth=\".+?\" />\s*)+)} - {&rel2abs($1)}ges; - -print STDOUT $str; -exit; - -# -# Definitions for helper sub-routines -# - -sub msg { - print STDERR shift, "\n"; -} - -sub rel2abs { - my $str = shift; - my $colnum = 1; - - my @widths = (); - my $total = 0; - my $output = ''; - - my $gutters; - my $content_width; - my $total_width; - my @num_cache; - - $str =~ /^(\s+)/; - my $ws = $1; - - while ($str =~ m/<colspec colwidth="(\d+)\*" \/>/g) { - $total += $1; - push @widths, $1; - } - - msg("!!! WARNING: Total Percent > 100%: $total%") if $total > 100; - - if (! $total) { - die 'Something bad has happened - the script believes that there are no columns'; - } - - $gutters = $#widths * $gutter_width; - $content_width = $table_width - $gutters; - # Don't forget that $#... is the last offset not the count - - foreach (@widths) { - my $temp = sprintf ("%0.2f", $_/100 * $content_width); - $total_width += $temp; - - if ($total_width > $content_width) { - $temp -= $total_width - $content_width; - msg("!!! WARNING: Column width reduced from " . - ($temp + ($total_width - $content_width)) . " to $temp !!!"); - $total_width -= $total_width - $content_width; - } - - $output .= $ws . '<colspec colnum="'. $colnum .'" colwidth="'. $temp .'cm" />' . "\n"; - ++$colnum; - push @num_cache, $temp; - } - - return $output . "\n$ws"; -} diff --git a/Docs/Support/docbook-fixup.pl b/Docs/Support/docbook-fixup.pl deleted file mode 100755 index 48ab085ad3e..00000000000 --- a/Docs/Support/docbook-fixup.pl +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/perl -w - -# Fix the output of `makeinfo --docbook` version 4.0c -# Convert the broken docbook output to well-formed XML that conforms to the O'Reilly idiom -# See code for detailed comments -# Authors: Arjen Lentz and Zak Greant (original code by Jeremy Cole) - -use strict; - -my $data = ''; -my @apx = (); -my $apx = ''; -my @nodes = (); -my $nodes = ''; - -msg ("-- Post-processing `makeinfo --docbook` output --"); -msg ("** Written to work with makeinfo version 4.0c **\n"); - -msg ("Discarding DTD - not required by subsequent scripts"); -# <> is a magic filehandle - either reading lines from stdin or from file(s) specified on the command line -<>; - -msg ("Create an XML PI with ISO-8859-1 character encoding"); -$data = "<?xml version='1.0' encoding='ISO-8859-1'?>"; - -msg ("Get the rest of the data"); -$data = $data . join "", <>; - -msg ("Add missing <bookinfo> and <abstract> opening tags"); -# Note the absence of the g (global) pattern modified. This situation can only happen once. -# ...as soon as we find the first instance, we can stop looking. -$data =~ s/<book lang="en">/<book lang="en"><bookinfo><abstract>/; - - -# arjen 2002-05-01 -msg ("Processing docbook-prefix special strings"); -$data =~ s/FIXUPmdashFIXUP/\&mdash\;/g; - -$data =~ s/FIXUPdoubledashFIXUP/--/g; - -$data =~ s/FIXUPstrongFIXUP/<emphasis\ role\=\"bold\">/g; -$data =~ s/FIXUPendstrongFIXUP/<\/emphasis>/g; - -$data =~ s/FIXUPemphFIXUP/<emphasis>/g; -$data =~ s/FIXUPendemphFIXUP/<\/emphasis>/g; - -$data =~ s/FIXUPfileFIXUP/<filename>/g; -$data =~ s/FIXUPendfileFIXUP/<\/filename>/g; - -$data =~ s/FIXUPsampFIXUP/<literal>/g; -$data =~ s/FIXUPendsampFIXUP/<\/literal>/g; - - -msg ("Removing mailto: from email addresses..."); -$data =~ s/mailto://g; - -msg ("Removing INFORMALFIGURE..."); -$data =~ s{<informalfigure>.+?</informalfigure>} - {}gs; - -msg ("Convert ampersand to XML escape sequence..."); -$data =~ s/&(?!\w+;)/&/g; - -# arjen 2002-05-01 -msg ("Changing (TM) to XML escape sequence..."); -$data =~ s/MySQL \(TM\)/MySQL™/g; -$data =~ s{<command>TM</command>} - {™}g; - -# arjen 2002-05-01 -msg ("Changing ' -- ' to XML escape sequence..."); -$data =~ s/ -- /—/g; - -msg ("Changing @@ to @..."); -$data =~ s/@@/@/g; - -msg ("Rework references of the notation '<n>'"); -# Need to talk to Arjen about what the <n> bits are for -$data =~ s/<(\d)>/[$1]/g; - -msg ("Changing '_' to '-' in references..."); -$data =~ s{((?:id|linkend)=\".+?\")} - {&underscore2hyphen($1)}gex; - -msg ("Changing ULINK to SYSTEMITEM..."); -$data =~ s{<ulink url=\"(.+?)\">\s*</ulink>} - {<systemitem role=\"url\">$1</systemitem>}gs; - -msg ("Adding PARA inside ENTRY..."); -$data =~ s{<entry>(.*?)</entry>} - {<entry><para>$1</para></entry>}gs; - -msg ("Fixing spacing problem with titles..."); -$data =~ s{(</\w+>)(\w{2,})} - {$1 $2}gs; - -msg ("Adding closing / to XREF and COLSPEC tags..."); -$data =~ s{<(xref|colspec) (.+?)>} - {<$1 $2 />}gs; - -# arjen 2002-04-26 -msg ("Removing separate target titles from LINKs and make them XREFs..."); -$data =~ s{<link (linkend=.+?)>.+?</link>} - {<xref $1 />}gs; - -# Probably need to strip these -msg ('Adding "See " to XREFs that used to be @xref...'); -$data =~ s{([.'!)])\s*<xref } - {$1 See <xref }gs; - -msg ('Adding "see " to (XREFs) that used to be (@pxref)...'); -$data =~ s{([([,;])(\s*)<xref } - {$1$2see <xref }gs; - -msg ("Making first row in table THEAD..."); -$data =~ s{( *)<tbody>(\s*<row>.+?</row>)} - {$1<thead>$2\n$1</thead>\n$1<tbody>}gs; - -msg ("Removing EMPHASIS inside THEAD..."); -$data =~ s{<thead>(.+?)</thead>} - {"<thead>".&strip_tag($1, 'emphasis')."</thead>"}gsex; - -msg ("Removing empty PARA..."); -$data =~ s{<para>\s*</para>} - {}gs; - -msg ("Removing lf before /PARA in ENTRY..."); -$data =~ s{\n(</para></entry>)} - {$1}gs; - -msg ("Removing whitespace before /PARA if not on separate line..."); -$data =~ s{(\S+)[\t ]+</para>} - {$1</para>}g; - -msg ("Removing PARA around INDEXTERM if no text in PARA..."); -$data =~ s{<para>((?:<indexterm role=\"[^"]+\">(?:<(primary|secondary)>[^>]+</\2>)+?</indexterm>)+?)\s*</para>} - {$1}gs; - -@apx = ("Users", "MySQL Testimonials", "News", "GPL-license", "LGPL-license"); - -foreach $apx (@apx) { - msg ("Removing appendix $apx..."); - $data =~ s{<appendix id=\"$apx\">(.+?)</appendix>} - {}gs; - - # Skip to next appendix regex if the regex did not match anything - next unless (defined $&); - - msg ("...Building list of removed nodes..."); - - # Split the last bracketed regex match into an array - # Extract the node names from the tags and push them into an array - foreach (split "\n", $&) { - push @nodes, $1 if /<\w+ id=\"(.+?)\">/ - } -} - -# 2002-02-22 arjen@mysql.com (added fix " /" to end of regex, to make it match) -msg ("Fixing references to removed nodes..."); -# Merge the list of node names into a set of regex alternations -$nodes = join "|", @nodes; - -# Find all references to removed nodes and convert them to absolute URLs -$data =~ s{<\w+ linkend="($nodes)" />} - {&xref2link($1)}ges; - -print STDOUT $data; -exit; - -# -# Definitions for helper sub-routines -# - -sub msg { - print STDERR "docbook-fixup:", shift, "\n"; -} - -sub strip_tag($$) { - (my $str, my $tag) = @_; - $str =~ s{<$tag>(.+?)</$tag>}{$1}gs; - return $str; -} - -sub underscore2hyphen($) { - my $str = shift; - $str =~ tr/_/-/; - return $str; -} - -sub xref2link { - my $ref = shift; - $ref =~ tr/ /_/; - $ref =~ s{^((.)(.).+)$}{$2/$3/$1.html}; - return "http://www.mysql.com/doc/" . $ref; -} - -# We might need to encode the high-bit characters to ensure proper representation -# msg ("Converting high-bit characters to entities"); -# $data =~ s/([\200-\400])/&get_entity($1)>/gs; -# There is no get_entity function yet - no point writing it til we need it :) diff --git a/Docs/Support/docbook-prefix.pl b/Docs/Support/docbook-prefix.pl deleted file mode 100755 index e76d84dbfe0..00000000000 --- a/Docs/Support/docbook-prefix.pl +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/perl -w - -# Preprocess the input of `makeinfo --docbook` version 4.0c -# Authors: Arjen Lentz and Zak Greant (started by arjen 2002-05-01) - -use strict; - -my $data = ''; - -msg ("-- Pre-processing `makeinfo --docbook` input --"); -msg ("** Written to work with makeinfo version 4.0c **\n"); - -# <> is a magic filehandle - either reading lines from stdin or from file(s) specified on the command line -msg ("Get the data"); -$data = join "", <>; - -msg ("Replacing '\@-' with FIXUPmdashFIXUP"); -$data =~ s/\@-/FIXUPmdashFIXUP/g; - -msg ("Replacing '--' with FIXUPdoubledashFIXUP"); -$data =~ s/--/FIXUPdoubledashFIXUP/g; - -msg ("Turning \@strong{} into LITERAL blocks"); -$data =~ s/\@strong\{(.*?)\}/FIXUPstrongFIXUP$1FIXUPendstrongFIXUP/gs; - -msg ("Turning \@emph{} into LITERAL blocks"); -$data =~ s/\@emph\{(.*?)\}/FIXUPemphFIXUP$1FIXUPendemphFIXUP/gs; - -msg ("Turning \@file{} into LITERAL blocks"); -$data =~ s/\@file\{(.*?)\}/FIXUPfileFIXUP$1FIXUPendfileFIXUP/gs; - -msg ("Turning \@samp{} into LITERAL blocks"); -$data =~ s/\@samp\{\@\{\}/FIXUPsampFIXUP\@\{FIXUPendsampFIXUP/g; -$data =~ s/\@samp\{\@\}\}/FIXUPsampFIXUP\@\}FIXUPendsampFIXUP/g; -$data =~ s/\@samp\{\@\{n\@\}\}/FIXUPsampFIXUP\@\{n\@\}FIXUPendsampFIXUP/g; -$data =~ s/\@samp\{(.*?)\}/FIXUPsampFIXUP$1FIXUPendsampFIXUP/gs; - - -msg ("Write the data"); -print STDOUT $data; -exit; - -# -# Definitions for helper sub-routines -# - -sub msg { - print STDERR "docbook-prefix: ", shift, "\n"; -} - diff --git a/Docs/Support/docbook-split b/Docs/Support/docbook-split deleted file mode 100755 index eafb437efe4..00000000000 --- a/Docs/Support/docbook-split +++ /dev/null @@ -1,70 +0,0 @@ -#! /usr/bin/perl -w -# O'Reilly's Perl script to chop mysql.xml into separate ch/apps/index files. -# The indexes are actually not used, they're created straight from the xrefs. -# Breaks the MySQL reference manual into chapters, appendices, and indexes. - -use strict; - -my $app_letter = "a"; # Start appendix letters at "a" -my $chap_num = 1; # Start chapter numbers at one (there is no preface) -my $directory = "mysql_refman_" . time; -my $ext = ".xml"; -my $line = ""; -my $output_name = ""; -my $start_text = ""; - -mkdir $directory unless -d $directory; - -while (defined $line) { - if ($line =~ /(<chapter.+)/i ) { - $start_text = $1; - $output_name = sprintf("ch%02d%s", $chap_num, $ext); - ++$chap_num; - &process_file("chapter"); - } - elsif ($line =~ /(<appendix.+)/i ) { - $start_text = $1 ; - $output_name = "app$app_letter$ext"; - ++$app_letter; - &process_file("appendix"); - } - elsif ($line =~ /(<index\s+id=")(.*?)(">.*)/i ) { - $start_text = $1 . $2 . $3; - $output_name = lc($2) . $ext; - &process_file("index"); - } - else { - # Skip junk in between chapters, appendices and indexes. - $line = <>; - } -} - -sub process_file { - my $marker = shift; - my $path = "$directory/$output_name"; - - open (OUTPUT_FILE, ">$path") or die "Cannot open $path"; - - print STDERR "Creating $path\n"; - - # Print out XML PI - print OUTPUT_FILE "<?xml version='1.0' encoding='ISO-8859-1'?>\n"; - - # Print whatever happened to appear at the end of the previous chapter. - print OUTPUT_FILE "$start_text\n" if $start_text; - - while (defined $line) { - $line = <>; - - # Note: Anything after the terminating marker is lost, just like - # lines in between chapters. - if ($line =~ /(.*<\/\s*$marker\s*>)/i ) { - print OUTPUT_FILE "$1\n" if $1; - close OUTPUT_FILE; - return; - } - print OUTPUT_FILE $line; - } -} - -exit 0; diff --git a/Docs/Support/generate-text-files.pl b/Docs/Support/generate-text-files.pl index 0829525f679..69e0478eb8a 100755 --- a/Docs/Support/generate-text-files.pl +++ b/Docs/Support/generate-text-files.pl @@ -1,4 +1,19 @@ #!/usr/bin/perl -w -*- perl -*- +# Copyright (C) 2000, 2003, 2005 MySQL AB +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + # Generate text files from top directory from the manual. $from = shift(@ARGV); diff --git a/Docs/Support/make-docbook b/Docs/Support/make-docbook deleted file mode 100755 index 93dbc56c0f8..00000000000 --- a/Docs/Support/make-docbook +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh -# 2002-01-30 arjen@mysql.com -# Use this to create mysql.xml (the DocBook XML format output of manual.texi) -# Requires makeinfo 4.0c - -#create include.texi with version/port # - echo "@c This file is autogenerated by the Makefile" > include.texi - echo -n "@set mysql_version " >> include.texi -# grep "AM_INIT_AUTOMAKE(mysql, " ../configure.in | \ -# sed -e 's;AM_INIT_AUTOMAKE(mysql, ;;' -e 's;);;' >> include.texi -# 2002-04-26 arjen - the below just picks #.# instead of #.#.#-alpha -# (code by mwagner - tnx) - grep "AM_INIT_AUTOMAKE(mysql, " ../configure.in | \ - perl -p -e 's/AM_INIT_AUTOMAKE\(mysql,\s(\d+\.\d+)\..+/$1/' >> include.texi - echo -n "@set default_port " >> include.texi - grep "MYSQL_TCP_PORT_DEFAULT=" ../configure.in | \ - sed -e 's;MYSQL_TCP_PORT_DEFAULT=;;' >> include.texi - -# produce DocBook XML - Support/docbook-prefix.pl < manual.texi |\ - makeinfo --force --no-ifinfo --docbook -o - |\ - Support/docbook-fixup.pl > mysql.xml - - # See if the XML output is well-formed - xmlwf mysql.xml - - # If all is well, keep processing - cat mysql.xml | Support/colspec-fix.pl | Support/docbook-split; - diff --git a/Docs/Support/make-makefile b/Docs/Support/make-makefile deleted file mode 100755 index 79cf06091fe..00000000000 --- a/Docs/Support/make-makefile +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -# Use this when you have deleted Makefile and do not want to do a full -# build to get it back - -cd .. -automake --gnu Docs/Makefile -CONFIG_FILES=Docs/Makefile CONFIG_HEADERS= sh ./config.status diff --git a/Docs/Support/test-make-manual b/Docs/Support/test-make-manual deleted file mode 100755 index bd4ed4b04e3..00000000000 --- a/Docs/Support/test-make-manual +++ /dev/null @@ -1,137 +0,0 @@ -#!/bin/sh - -needed_flags=0 -needed_texi2html=0 -needed_texinfo_tex=0 -needed_include_texi=0 - -if [ -z $BROWSER ]; then - BROWSER=netscape - echo "BROWSER not set, using $BROWSER" -fi - -die () -{ - echo - echo $1 - cleanup - exit 1 -} - -cleanup () -{ - echo "Cleaning up..." - if [ $needed_flags ]; then - bk clean Flags - fi - - if [ $needed_texi2html ]; then - bk clean Support/texi2html - fi - - if [ $needed_texinfo_tex ]; then - bk clean Support/texinfo.tex - fi - - if [ $needed_include_texi ]; then - rm -f include.texi - fi - - for file in \ - manual.aux manual.cp manual.cps manual.dvi \ - manual.fn manual.fns manual.ky manual.html \ - manual.pg manual.toc manual.tp manual.vr \ - mysql.info manual_toc.html ; - do - rm -f $file - done - -} - - -if [ -e Flags/usa.txt ]; then - echo "Good, Flags are there." -else - echo -n "Checking out Flags..." - bk edit Flags >/dev/null 2>&1 - echo " Done." - needed_flags=1 -fi - -if [ -e Support/texi2html ]; then - echo "Good, texi2html is there." -else - echo -n "Checking out texi2html..." - bk edit Support/texi2html >/dev/null 2>&1 - echo " Done." - needed_texi2html=1 -fi - -if [ -e Support/texinfo.tex ]; then - echo "Good, texinfo.tex is there." -else - echo -n "Checking out texinfo.tex..." - bk edit Support/texinfo.tex >/dev/null 2>&1 - echo " Done." - needed_texinfo_tex=1 -fi - -if [ -e include.texi ]; then - echo "Good, include.texi is there." -else - echo -n "Creating include.texi..." - bk edit ../configure.in >/dev/null 2>&1 - echo "@c This file was generated by test-make-manual" > include.texi - echo -n "@set mysql_version " >> include.texi - grep "AM_INIT_AUTOMAKE(mysql, " ../configure.in | \ - sed -e 's;AM_INIT_AUTOMAKE(mysql, ;;' -e 's;);;' >> include.texi - echo -n "@set default_port " >> include.texi - grep "MYSQL_TCP_PORT_DEFAULT=" ../configure.in | \ - sed -e 's;MYSQL_TCP_PORT_DEFAULT=;;' >> include.texi - echo " Done." - needed_include_texi=1 -fi - -echo -n "Running makeinfo..." -makeinfo --no-split -I . manual.texi - -if [ $? != 0 ]; then - die "Manual has errors - fix before you commit" -else - echo " Looks good." -fi - - -echo -n "Running texi2html..." -/usr/bin/perl ./Support/texi2html -iso -number manual.texi - -if [ $? != 0 ]; then - die "Manual has errors - fix before you commit" -else - echo " Looks good." -fi - - -echo -n "Running texi2dvi..." -texi2dvi --batch manual.texi > texi2dvi.out - -if [ $? != 0 ]; then - die "Manual has errors - fix before you commit (saved in texi2dvi.out)" -else - rm texi2dvi.out - echo " Looks good." -fi - -echo -echo -echo "Please examine your modifications in \`manual.html'." -echo -echo "If you would like to use a different browser, set the 'BROWSER' environment" -echo "variable." -echo - -$BROWSER file:`pwd`/manual_toc.html - -echo "-- Press Enter to Continue --" -read junk -cleanup diff --git a/Docs/Support/test-make-manual-de b/Docs/Support/test-make-manual-de deleted file mode 100755 index a5c03001bda..00000000000 --- a/Docs/Support/test-make-manual-de +++ /dev/null @@ -1,137 +0,0 @@ -#!/bin/sh - -needed_flags=0 -needed_texi2html=0 -needed_texinfo_tex=0 -needed_include_texi=0 - -if [ -z $BROWSER ]; then - BROWSER=netscape - echo "BROWSER not set, using $BROWSER" -fi - -die () -{ - echo - echo $1 - cleanup - exit 1 -} - -cleanup () -{ - echo "Cleaning up..." - if [ $needed_flags ]; then - bk clean Flags - fi - - if [ $needed_texi2html ]; then - bk clean Support/texi2html - fi - - if [ $needed_texinfo_tex ]; then - bk clean Support/texinfo.tex - fi - - if [ $needed_include_texi ]; then - rm -f include.texi - fi - - for file in \ - manual.de.aux manual.de.cp manual.de.cps manual.de.dvi \ - manual.de.fn manual.de.fns manual.de.ky manual.de.html \ - manual.de.pg manual.de.toc manual.de.tp manual.de.vr \ - mysql.de.info manual.de_toc.html ; - do - rm -f $file - done - -} - - -if [ -e Flags/usa.txt ]; then - echo "Good, Flags are there." -else - echo -n "Checking out Flags..." - bk edit Flags >/dev/null 2>&1 - echo " Done." - needed_flags=1 -fi - -if [ -e Support/texi2html ]; then - echo "Good, texi2html is there." -else - echo -n "Checking out texi2html..." - bk edit Support/texi2html >/dev/null 2>&1 - echo " Done." - needed_texi2html=1 -fi - -if [ -e Support/texinfo.tex ]; then - echo "Good, texinfo.tex is there." -else - echo -n "Checking out texinfo.tex..." - bk edit Support/texinfo.tex >/dev/null 2>&1 - echo " Done." - needed_texinfo_tex=1 -fi - -if [ -e include.texi ]; then - echo "Good, include.texi is there." -else - echo -n "Creating include.texi..." - bk edit ../configure.in >/dev/null 2>&1 - echo "@c This file was generated by test-make-manual" > include.texi - echo -n "@set mysql_version " >> include.texi - grep "AM_INIT_AUTOMAKE(mysql, " ../configure.in | \ - sed -e 's;AM_INIT_AUTOMAKE(mysql, ;;' -e 's;);;' >> include.texi - echo -n "@set default_port " >> include.texi - grep "MYSQL_TCP_PORT_DEFAULT=" ../configure.in | \ - sed -e 's;MYSQL_TCP_PORT_DEFAULT=;;' >> include.texi - echo " Done." - needed_include_texi=1 -fi - -echo -n "Running makeinfo..." -makeinfo --no-split -I . manual.de.texi - -if [ $? != 0 ]; then - die "Manual has errors - fix before you commit" -else - echo " Looks good." -fi - - -echo -n "Running texi2html..." -/usr/bin/perl ./Support/texi2html -iso -number manual.de.texi - -if [ $? != 0 ]; then - die "Manual has errors - fix before you commit" -else - echo " Looks good." -fi - - -echo -n "Running texi2dvi..." -texi2dvi --batch manual.de.texi > texi2dvi.out - -if [ $? != 0 ]; then - die "Manual has errors - fix before you commit (saved in texi2dvi.out)" -else - rm texi2dvi.out - echo " Looks good." -fi - -echo -echo -echo "Please examine your modifications in \`manual.de.html'." -echo -echo "If you would like to use a different browser, set the 'BROWSER' environment" -echo "variable." -echo - -$BROWSER file:`pwd`/manual.de_toc.html - -echo "-- Press Enter to Continue --" -read junk -cleanup diff --git a/Docs/Support/xwf b/Docs/Support/xwf deleted file mode 100755 index 38f89774fe8..00000000000 --- a/Docs/Support/xwf +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/perl -w -# -# Parse document and report first syntax (well-formedness) error found. -# - -use strict; -use XML::Parser; -use Getopt::Std; - -my %opts; -getopts('e', \%opts); -my $ENTREFS = exists( $opts{'e'} ); # flag: check ent refs - -my $parser = XML::Parser->new( - ErrorContext => 2, # output error context - ); - -# get input from files -if( @ARGV ) { - foreach( @ARGV ) { - my $file = $_; - unless( -r $file ) { - print STDERR "ERROR: Can't open '$file'.\n"; - return; - } - my $input = ''; - open( F, $file ); - while( <F> ) { $input .= $_; } - close F; - - # parse and report errors - if( &parse_string( $input )) { - print STDERR "ERROR in $file:\n$@\n"; - } else { - print STDERR "'$file' is well-formed.\n"; - } - } - print "All files checked.\n"; - -# get input from STDIN -} else { - my $input = ""; - while( <STDIN> ) { $input .= $_; } - if( &parse_string( $input )) { - print STDERR "ERROR in stream:\n$@\n"; - } else { - print STDERR "No syntax errors found in XML stream.\n"; - } -} - - -# parse the string and return error message -# -# NOTE: By default, entity refs are not expanded. XML::Parser can be -# told not to expand entity refs, but will still try to find -# replacement text just in case, which we don't want. Therefore, we -# need to do a stupid regexp replacement, removing entities from input. -# -sub parse_string { - my $string = shift; - unless( $ENTREFS ) { - $string =~ s/\&[^\s;]+;//g; # remove entity references - } - eval { $parser->parse( $string ); }; - $@ =~ s/at \/.*?$//s; # remove module line number - return $@; -} diff --git a/Docs/manual.chm b/Docs/manual.chm new file mode 100644 index 00000000000..28c3e1b5a86 --- /dev/null +++ b/Docs/manual.chm @@ -0,0 +1,14 @@ + +********************************************************* + +This is a dummy placeholder file for "manual.chm" in the +MySQL source trees. + +Note, that the documentation has been moved into a separate +BitKeeper source tree named "mysqldoc" - do not attempt to edit this +file! All changes to it should be done in the mysqldoc tree. + +This dummy file is being replaced with the actual file from the +mysqldoc tree when building the official source distribution. + +********************************************************* diff --git a/Docs/sp-imp-spec.txt b/Docs/sp-imp-spec.txt new file mode 100644 index 00000000000..ac17a375926 --- /dev/null +++ b/Docs/sp-imp-spec.txt @@ -0,0 +1,1100 @@ + + Implementation specification for Stored Procedures + ================================================== + + +- How parsing and execution of queries work + + In order to execute a query, the function sql_parse.cc:mysql_parse() is + called, which in turn calls the parser (yyparse()) with an updated Lex + structure as the result. mysql_parse() then calls mysql_execute_command() + which dispatches on the command code (in Lex) to the corresponding code for + executing that particular query. + + There are three structures involved in the execution of a query which are of + interest to the stored procedure implementation: + + - Lex (mentioned above) is the "compiled" query, that is the output from + the parser and what is then interpreted to do the actual work. + It constains an enum value (sql_command) which is the query type, and + all the data collected by the parser needed for the execution (table + names, fields, values, etc). + - THD is the "run-time" state of a connection, containing all that is + needed for a particular client connection, and, among other things, the + Lex structure currently being executed. + - Item_*: During parsing, all data is translated into "items", objects of + the subclasses of "Item", such as Item_int, Item_real, Item_string, etc, + for basic datatypes, and also various more specialized Item types for + expressions to be evaluated (Item_func objects). + + +- How to fit Stored Procedure into this scheme + + - An overview of the classes and files for stored procedures + (More detailed APIs at the end of this file) + + - class sp_head (sp_head.{cc,h}) + This contains, among other things, an array of "instructions" and the + method for executing the procedure. + + - class sp_pcontext (sp_pcontext.{cc,h} + This is the parse context for the procedure. It's primarily used during + parsing to keep track of local parameters, variables and labels, but + it's also used at CALL time do find parameters mode (IN, OUT or INOUT) + and type when setting up the runtime context. + + - class sp_instr (sp_head.{cc,h}) + This is the base class for "instructions", that is, what is generated + by the parser. It turns out that we only need a minimum of 5 different + sub classes: + - sp_instr_stmt + Execute a statement. This is the "call-out" any normal SQL statement, + like a SELECT, INSERT etc. It contains the Lex structure for the + statement in question. + - sp_instr_set + Set the value of a local variable (or parameter) + - sp_instr_jump + An unconditional jump. + - sp_instr_jump_if_not + Jump if condition is not true. It turns out that the negative test is + most convenient when generating the code for the flow control + constructs. + - sp_instr_freturn + Return a value from a FUNCTION and exit. + For condition HANDLERs some special instructions are also needed, see + that section below. + + - class sp_rcontext (sp_rcontext.h) + This is the runtime context in the THD structure. + It contains an array of items, the parameters and local variables for + the currently executing stored procedure. + This means that variable value lookup is in runtime is constant time, + a simple index operation. + + - class Item_splocal (Item.{cc,h}) + This is a subclass of Item. Its sole purpose is to hide the fact that + the real Item is actually in the current frame (runtime context). + It contains the frame offset and defers all methods to the real Item + in the frame. This is what the parser generates for local variables. + + - Utility functions (sp.{cc,h}) + This contains functions for creating, dropping and finding a stored + procedure in the mysql.proc table (or the internal cache). + + + - Parsing CREATE PROCEDURE ... + + When parsing a CREATE PROCEDURE the parser first initializes the + sphead and spcont (runtime context) fields in the Lex. + The sql_command code for the result of parsing a is + SQLCOM_CREATE_PROCEDURE. + + The parsing of the parameter list and body is relatively + straight-forward: + + - Parameters: + name, type and mode (IN/OUT/INOUT) is pushed to spcont + - Declared local variables: + Same as parameters (mode is then IN) + - Local Variable references: + If an identifier is found in in spcont, an Item_splocal is created + with the variable's frame index, otherwise an Item_field or Item_ref + is created (as before). + - Statements: + The Lex in THD is replaced by a new Lex structure and the statement, + is parsed as usual. A sp_instr_stmt is created, containing the new + Lex, and added to added to the instructions in sphead. + Afterwards, the procedure's Lex is restored in THD. + - SET var: + Setting a local variable generates a sp_instr_set instruction, + containing the variable's frame offset, the expression (an Item), + and the type. + - Flow control: + Flow control constructs like, IF, WHILE, etc, generate a conditional + and unconditional jumps in the "obvious" way, but a few notes may + be required: + - Forward jumps: When jumping forward, the exact destination is not + known at the time of the creation of the jump instruction. The + sphead therefore contains list of instruction-label pairs for + each forward reference. When the position later is known, the + instructions in the list are updated with the correct location. + - Loop constructs have optional labels. If a loop doesn't have a + label, an anonymous label is generated to simplify the parsing. + - There are two types of CASE. The "simple" case is implemented + with an anonymous variable bound to the value to be tested. + + + - A simple example + + Parsing the procedure: + + create procedure a(s char(16)) + begin + declare x int; + set x = 3; + while x > 0 do + set x = x-1; + insert into db.tab values (x, s); + end while; + end + + would generate the following structures: + ______ + thd: | | _________ + | lex -+--->| | ___________________ + |______| | spcont -+------------------->| "s",in,char(16):0 | + | sphead -+------ |("x",in,int :1)| + |_________| | |___________________| + ____V__________________ + | m_name: "a" | + | m_defstr: "create ..."| + | m_instr: ... | + |_______________________| + + Note that the contents of the spcont is changing during the parsing, + at all times reflecting the state of the would-be runtime frame. + The m_instr is an array of instructions: + + Pos. Instruction + 0 sp_instr_set(1, '3') + 1 sp_instr_jump_if_not(5, 'x>0') + 2 sp_instr_set(1, 'x-1') + 3 sp_instr_stmt('insert into ...') + 4 sp_instr_jump(1) + 5 <end> + + Here, '3', 'x>0', etc, represent the Items or Lex for the respective + expressions or statements. + + + - Parsing CREATE FUNCTION ... + + Creating a functions is essensially the same thing as for a PROCEDURE, + with the addition that a FUNCTION has a return type and a RETURN + statement, but no OUT or INOUT parameters. + + The main difference during parsing is that we store the result type + in the sp_head. However, there are big differences when it comes to + invoking a FUNCTION. (See below.) + + + - Storing, caching, dropping... + + As seen above, the entired definition string, including the "CREATE + PROCEDURE" (or "FUNCTION") is kept. The procedure definition string is + stored in the table mysql.proc with the name and type as the key, the + type being one of the enum ("procedure","function"). + + A PROCEDURE is just stored in the mysql.proc table. A FUNCTION has an + additional requirement. They will be called in expressions with the same + syntax as UDFs, so UDFs and stored FUNCTIONs share the namespace. Thus, + we must make sure that we do not have UDFs and FUNCTIONs with the same + name (even if they are storded in different places). + + This means that we can reparse the procedure as many time as we want. + The first time, the resulting Lex is used to store the procedure in + the database (using the function sp.c:sp_create_procedure()). + + The simplest way would be to just leave it at that, and re-read the + procedure from the database each time it is called. (And in fact, that's + the way the earliest implementation will work.) + However, this is not very efficient, and we can do better. The full + implementation should work like this: + + 1) Upon creation time, parse and store the procedure. Note that we still + need to parse it to catch syntax errors, but we can't check if called + procedures exists for instance. + 2) Upon first CALL, read from the database, parse it, and cache the + resulting Lex in memory. This time we can do more error checking. + 3) Upon subsequent CALLs, use the cached Lex. + + Note that this implies that the Lex structure with its sphead must be + reentrant, that is, reusable and shareable between different threads + and calls. The runtime state for a procedure is kept in the sp_rcontext + in THD. + + The mechanisms of storing, finding, and dropping procedures are + encapsulated in the files sp.{cc,h}. + + + - CALLing a procedure + + A CALL is parsed just like any statement. The resulting Lex has the + sql_command SQLCOM_CALL, the procedure's name and the parameters are + pushed to the Lex' value_list. + + sql_parse.cc:mysql_execute_command() then uses sp.cc:sp_find() to + get the sp_head for the procedure (which may have been read from the + database or feetched from the in-memory cache) and calls the sp_head's + method execute(). + Note: It's important that substatements called by the procedure do not + do send_ok(). Fortunately, there is a flag in THD->net to disable + this during CALLs. If a substatement fails, it will however send + an error back to the client, so the CALL mechanism must return + immediately and without sending an error. + + The sp_head::execute() method works as follows: + + 1) Keep a pointer to the old runtime context in THD (if any) + 2) Create a new runtime context. The information about the required size + is in sp_head's parse time context. + 3) Push each parameter (from the CALL's Lex->value_list) to the new + context. If it's an OUT or INOUT parameter, the parameter's offset + in the caller's frame is set in the new context as well. + 4) For each instruction, call its execute() method. + The result is a pointer to the next instruction to execute (or NULL) + if an error occured. + 5) On success, set the new values of the OUT and INOUT parameters in + the caller's frame. + + - USE database + + Before executing the instruction we also keeps the current default + database (if any). If this was changed during execution (i.e. a "USE" + statement has been executed), we restore the current database to the + original. + + This is the most useful way to handle USE in procedures. If we didn't, + the caller would find himself in a different database after calling + a function, which can be confusing. + Restoring the database also gives full freedom to the procedure writer: + - It's possible to write "general" procedures that are independent of + the actual database name. + - It's possible to write procedures that work on a particular database + by calling USE, without having to use fully qualified table names + everywhere (which doesn't help if you want to call other, "general", + procedures anyway). + + - Evaluating Items + + There are three occasions where we need to evaluate an expression: + + - When SETing a variable + - When CALLing a procedure + - When testing an expression for a branch (in IF, WHILE, etc) + + The semantics in stored procedures is "call-by-value", so we have to + evaluate any "func" Items at the point of the CALL or SET, otherwise + we would get a kind of "lazy" evaluation with unexpected results with + respect to OUT parameters for instance. + For this the support function, sp_head.cc:eval_func_item() is needed. + + + - Calling a FUNCTION + + Functions don't have an explicit call keyword like procedures. Instead, + they appear in expressions with the conventional syntax "fun(arg, ...)". + The problem is that we already have User Defined Functions (UDFs) which + are called the same way. A UDF is detected by the lexical analyzer (not + the parser!), in the find_keyword() function, and returns a UDF_*_FUNC + or UDA_*_SUM token with the udf_func object as the yylval. + + So, stored functions must be handled in a simpilar way, and as a + consequence, UDFs and functions must not have the same name. + + - Detecting and parsing a FUNCTION invocation + + The existance of UDFs are checked during the lexical analysis (in + sql_lex.cc:find_keyword()). This has the drawback that they must + exist before they are refered to, which was ok before SPs existed, + but then it becomes a problem. The first implementation of SP FUNCTIONs + will work the same way, but this should be fixed a.s.a.p. (This will + required some reworking of the way UDFs are handled, which is why it's + not done from the start.) + For the time being, a FUNCTION is detected the same way, and returns + the token SP_FUNC. During the parsing we only check for the *existance* + of the function, we don't parse it, since wa can't call the parser + recursively. + + When encountering a SP_FUNC with parameters in the expression parser, + an instance of the new Item_func_sp class is created. Unlike UDFs, we + don't have different classes for different return types, since we at + this point don't know the type. + + - Collecting FUNCTIONs to invoke + + A FUNCTION differs from a PROCEDURE in one important aspect: Whereas a + PROCEDURE is CALLed as statement by itself, a FUNCTION is invoked + "on-the-fly" during the execution of *another* statement. + This makes things a lot more complicated compared to CALL: + - We can't read and parse the FUNCTION from the mysql.proc table at the + point of invocation; the server requires that all tables used are + opened and locked at the beginning of the query execution. + One "obvious" solution would be to simply push "mysql.proc" to the list + of tables used by the query, but this implies a "join" with this table + if the query is a select, so it doesn't work (and we can't exclude this + table easily; since a priviledged used might in fact want to search + the proc table). + Another solution would of course be to allow the opening and closing + of the mysql.proc table during a query execution, but this it not + possible at the present. + + So, the solution is to collect the names of the refered FUNCTIONs during + parsing in the lex. + Then, before doing anything else in mysql_execute_command(), read all + functions from the database an keep them in the THD, where the function + sp_find_function() can find them during the execution. + Note: Even with an in-memory cache, we must still make sure that the + functions are indeed read and cached at this point. + The code that read and cache functions from the database must also be + invoked recursively for each read FUNCTION to make sure we have *all* the + functions we need. + + + - Parsing DROP PROCEDURE/FUNCTION + + The procedure name is pushed to Lex->value_list. + The sql_command code for the result of parsing a is + SQLCOM_DROP_PROCEDURE/SQLCOM_DROP_FUNCTION. + + Dropping is done by simply getting the procedure with the sp_find() + function and calling sp_drop() (both in sp.{cc,h}). + + DROP PROCEDURE/FUNCTION also supports the non-standard "IF EXISTS", + analogous to other DROP statements in MySQL. + + + - Condition and Handlers + + Condition names are lexical entities and are kept in the parser context + just like variables. But, condition are just "aliases" for SQLSTATE + strings, or mysqld error codes (which is a non-standard extension in + MySQL), and are only used during parsing. + + Handlers comes in three types, CONTINUE, EXIT and UNDO. The latter is + like an EXIT handler with an implicit rollback, and is currently not + implemented. + The EXIT handler jumps to the end of its BEGIN-END block when finished. + The CONTINUE handler returns to the statement following that which + invoked the handler. + + The handlers in effect at any point is part of each thread's runtime + state, so we need to push and pop handlers in the sp_rcontext during + execution. We use special instructions for this: + - sp_instr_hpush_jump + Push a handler. The instruction contains the necessary information, + like which conditions we handle and the location of the handler. + The jump takes us to the location after the handler code. + - sp_instr_hpop + Pop the handlers of the current frame (which we are just leaving). + + It might seems strange to jump past the handlers like that, but there's + no extra cost in doing this, and for technical reasons it's easiest for + the parser to generate the handler instructions when they occur in the + source. + + When an error occurs, one of the error routines is called and an error + message is normally sent back to the client immediately. + Catching a condition must be done in these error routines (there are + quite a few) to prevent them from doing this. We do this by calling + a method in the THD's sp_rcontext (if there is one). If a handler is + found, this is recorded in the context and the routine returns without + sending the error message. + The exectution loop (sp_head::execute()) checks for this after each + statement and invokes the handler that has been found. If several + errors or warnings occurs during one statement, only the first is + caught, the rest are ignored. + + Invoking and returning from a handler is trivial in the EXIT case. + We simply jump to it, and it will have an sp_instr_jump as its last + instruction. + + Calling and returning from a CONTINUE handler poses some special + problems. Since we need to return to the point after its invokation, + we push the return location on a stack in the sp_rcontext (this is + done by the exectution loop). The handler then ends with a special + instruction, sp_instr_hreturn, which returns to this location. + + CONTINUE handlers have one additional problem: They are parsed at + the lexical level where they occur, so variable offsets will assume + that it's actually called at that level. However, a handler might be + invoked from a sub-block where additional local variables have been + declared, which will then share the location of any local variables + in the handler itself. So, when calling a CONTINUE handler, we need + to save any local variables above the handler's frame offset, and + restore them upon return. (This is not a problem for EXIT handlers, + since they will leave the block anyway.) + This is taken care of by the execution loop and the sp_instr_hreturn + instruction. + + - Examples: + + - EXIT handler + begin + declare x int default 0; + + begin + declare exit handler for 'XXXXX' set x = 1; + + (statement1); + (statement2); + end; + (statement3); + end + + Pos. Instruction + 0 sp_instr_set(0, '0') + 1 sp_instr_hpush_jump(4, 1) # location and frame size + 2 sp_instr_set(0, '1') + 3 sp_instr_jump(6) + 4 sp_instr_stmt('statement1') + 5 sp_instr_stmt('statement2') + 6 sp_instr_hpop(1) + 7 sp_instr_stmt('statement3') + + - CONTINUE handler + create procedure hndlr1(val int) + begin + declare x int default 0; + declare foo condition for 1146; + declare continue handler for foo set x = 1; + + insert into t3 values ("hndlr1", val); # Non-existing table? + if x>0 then + insert into t1 values ("hndlr1", val); # This instead then + end if; + end| + + Pos. Instruction + 0 sp_instr_set(1, '0') + 1 sp_instr_hpush_jump(4, 2) + 2 sp_instr_set(1, '1') + 3 sp_instr_hreturn(2) # frame size + 4 sp_instr_stmt('insert ... t3 ...') + 5 sp_instr_jump_if_not(7, 'x>0') + 6 sp_instr_stmt('insert ... t1 ...') + 7 sp_instr_hpop(2) + + + - Cursors + + For stored procedures to be really useful, you want to have cursors. + MySQL doesn't yet have "real" cursor support (with API and ODBC support, + allowing updating, arbitrary scrolling, etc), but a simple asensitive, + non-scrolling, read-only cursor can be implemented in SPs using the + class Protocol_cursor. + This class intecepts the creation and sending of results sets and instead + stores it in-memory, as MYSQL_FIELDS and MYSQL_ROWS (as in the client API). + + To support this, we need the usual name binding support in sp_pcontext + (similar to variables and conditions) to keep track on declared cursor + names, and a corresponding run-time mechanism in sp_rcontext. + Cursors are lexically scoped like everything with a body or BEGIN/END + block, so they are pushed and poped as usual (see conditions and variables + above). + The basic operations on a cursor are OPEN, FETCH and CLOSE, which will + each have a corresponding instruction. In addition, we need instructions + to push a new cursor (this will encapsulate the LEX of the SELECT statement + of the cursor), and a pop instruction: + - sp_instr_cpush + Push a cursor to the sp_rcontext. This instruction contains the LEX + for the select statement + - sp_instr_cpop + Pop a number of cursors from the sp_rcontext. + - sp_instr_copen + Open a cursor: This will execute the select and get the result set + in a sepeate memroot. + - sp_instr_cfetch + Fetch the next row from the in-memory result set. The instruction + contains a list of the variables (frame offsets) to set. + - sp_instr_cclose + Free the result set. + + A cursor is a separate class, sp_cursor (defined in sp_rcontex.h) which + encapsulates the basic operations used by the above instructions. + This class contains the LEX, Protocol_cursor object, and its memroot, + as well as the cursor's current state. + Compiling and executing is fairly straight-forward. sp_instr_copen is + a subclass of sp_instr_stmt and uses its mechanism to execute a + substatement. + + - Example: + + begin + declare x int; + declare c cursor for select a from t1; + + open c; + fetch c into x; + close c; + end + + Pos. Instruction + 0 sp_instr_cpush('select a from ...') + 1 sp_instr_copen(0) # The 0'th cursor + 2 sp_instr_cfetch(0) # Contains the variable list + 3 sp_instr_cclose(0) + 4 sp_instr_cpop(1) + + + + - The SP cache + + There are two ways to cache SPs: + + 1) one global cache, share by all threads/connections, + 2) one cache per thread. + + There are pros and cons with both methods: + + 1) Pros: Save memory, each SP only read from table once, + Cons: Needs locking (= serialization at access), requires thread-safe + data structures, + 2) Pros: Fast, no locking required (almost), limited thread-safe + requirement, + Cons: Uses more memory, each SP read from table once per thread. + + Unfortunately, we cannot use alternative 1 for the time being, as most + of the datastructures to be cached (lex and items) are not reentrant + and thread-safe. (Things are modifed at execution, we have THD pointers + stored everywhere, etc.) + This leaves us with alternative 2, one cache per thread; or actually + two, since we keep FUNCTIONs and PROCEDUREs in separate caches. + This is not that terrible; the only case when it will perform + significantly worse than a global cache is when we have an application + where new threads are connecting, calling a procedure, and disconnecting, + over and over again. + + The cache implementation itself is simple and straightforward, a hashtable + wrapped in a class and a C API (see APIs below). + + There is however one issue with multiple caches: dropping and altering + procedures. Normally, this should be a very rare event in a running + system; it's typically something you do during development and testing, + so it's not unthinkable that we would simply ignore the issue and let + any threads running with a cached version of an SP keep doing so until + its disconnected. + But assuming we want to keep the caches consistent with respect to drop + and alter, it can be done: + + 1) A global counter is needed, initialized to 0 at start. + 2) At each DROP or ALTER, increase the counter by one. + 3) Each cache has its own copy of the counter, copied at the last read. + 4) When looking up a name in the cache, first check if the global counter + is larger than the local copy. + If so, clear the cache and return "not found", and update the local + counter; otherwise, lookup as usual. + + This minimizes the cost to a single brief lock for the access of an + integer when operating normally. Only in the event of an actual drop or + alter, is the cache cleared. This may seem to be drastic, but since we + assume that this is a rare event, it's not a problem. + It would of course be possible to have a much more fine-grained solution, + keeping track of each SP, but the overhead of doing so is not worth the + effort. + + + - Class and function APIs + This is an outline of the key types. Some types and other details + in the actual files have been omitted for readability. + + - The parser context: sp_pcontext.h + + typedef enum + { + sp_param_in, + sp_param_out, + sp_param_inout + } sp_param_mode_t; + + typedef struct + { + LEX_STRING name; + enum enum_field_types type; + sp_param_mode_t mode; + uint offset; // Offset in current frame + my_bool isset; + } sp_pvar_t; + + typedef struct sp_cond_type + { + enum { number, state, warning, notfound, exception } type; + char sqlstate[6]; + uint mysqlerr; + } sp_cond_type_t; + + class sp_pcontext + { + sp_pcontext(); + + // Return the maximum frame size + uint max_framesize(); + + // Return the current frame size + uint current_framesize(); + + // Return the number of parameters + uint params(); + + // Set the number of parameters to the current frame size + void set_params(); + + // Set type of the variable at offset 'i' in the frame + void set_type(uint i, enum enum_field_types type); + + // Mark the i:th variable to "set" (i.e. having a value) with + // 'val' true. + void set_isset(uint i, my_bool val); + + // Push the variable 'name' to the frame. + void push_var(LEX_STRING *name, + enum enum_field_types type, sp_param_mode_t mode); + + // Pop 'num' variables from the frame. + void pop_var(uint num = 1); + + // Find variable by name + sp_pvar_t *find_pvar(LEX_STRING *name); + + // Find variable by index + sp_pvar_t *find_pvar(uint i); + + // Push label 'name' of instruction index 'ip' to the label context + sp_label_t *push_label(char *name, uint ip); + + // Find label 'name' in the context + sp_label_t *find_label(char *name); + + // Return the last pushed label + sp_label_t *last_label(); + + // Return and remove the last pushed label. + sp_label_t *pop_label(); + + // Push a condition to the context + void push_cond(LEX_STRING *name, sp_cond_type_t *val); + + // Pop a 'num' condition from the context + void pop_cond(uint num); + + // Find a condition in the context + sp_cond_type_t *find_cond(LEX_STRING *name); + + // Increase the handler count + void add_handler(); + + // Returns the handler count + uint handlers(); + + // Push a cursor + void push_cursor(LEX_STRING *name); + + // Find a cursor + my_bool find_cursor(LEX_STRING *name, uint *poff); + + // Pop 'num' cursors + void pop_cursor(uint num); + + // Return the number of cursors + uint cursors(); + } + + + - The run-time context (call frame): sp_rcontext.h + + #define SP_HANDLER_NONE 0 + #define SP_HANDLER_EXIT 1 + #define SP_HANDLER_CONTINUE 2 + #define SP_HANDLER_UNDO 3 + + typedef struct + { + struct sp_cond_type *cond; + uint handler; // Location of handler + int type; + uint foffset; // Frame offset for the handlers declare level + } sp_handler_t; + + class sp_rcontext + { + // 'fsize' is the max size of the context, 'hmax' the number of handlers, + // 'cmax' the number of cursors + sp_rcontext(uint fsize, uint hmax, , uint cmax); + + // Push value (parameter) 'i' to the frame + void push_item(Item *i); + + // Set slot 'idx' to value 'i' + void set_item(uint idx, Item *i); + + // Return the item in slot 'idx' + Item *get_item(uint idx); + + // Set the "out" index 'oidx' for slot 'idx. If it's an IN slot, + // use 'oidx' -1. + void set_oindex(uint idx, int oidx); + + // Return the "out" index for slot 'idx' + int get_oindex(uint idx); + + // Set the FUNCTION result + void set_result(Item *i); + + // Get the FUNCTION result + Item *get_result(); + + // Push handler at location 'h' for condition 'cond'. 'f' is the + // current variable frame size. + void push_handler(sp_cond_type_t *cond, uint h, int type, uint f); + + // Pop 'count' handlers + void pop_handlers(uint count); + + // Find a handler for this error. This sets the state for a found + // handler in the context. If called repeatedly without clearing, + // only the first call's state is kept. + int find_handler(uint sql_errno); + + // Returns 1 if a handler has been found, with '*ip' and '*fp' set + // to the handler location and frame size respectively. + int found_handler(uint *ip, uint *fp); + + // Clear the found handler state. + void clear_handler(); + + // Push a return address for a CONTINUE handler + void push_hstack(uint ip); + + // Pop the CONTINUE handler return stack + uint pop_hstack(); + + // Save variables from frame index 'fp' and up. + void save_variables(uint fp); + + // Restore saved variables from to frame index 'fp' and up. + void restore_variables(uint fp); + + // Push a cursor for the statement (lex) + void push_cursor(LEX *lex); + + // Pop 'count' cursors + void pop_cursors(uint count); + + // Pop all cursors + void pop_all_cursors(); + + // Get the 'i'th cursor + sp_cursor *get_cursor(uint i); + + } + + + - The procedure: sp_head.h + + #define TYPE_ENUM_FUNCTION 1 + #define TYPE_ENUM_PROCEDURE 2 + + class sp_head + { + int m_type; // TYPE_ENUM_FUNCTION or TYPE_ENUM_PROCEDURE + + sp_head(); + + void init(LEX_STRING *name, LEX *lex, LEX_STRING *comment, char suid); + + // Store this procedure in the database. This is a wrapper around + // the function sp_create_procedure(). + int create(THD *); + + // Invoke a FUNCTION + int + execute_function(THD *thd, Item **args, uint argcount, Item **resp); + + // CALL a PROCEDURE + int + execute_procedure(THD *thd, List<Item> *args); + + // Add the instruction to this procedure. + void add_instr(sp_instr *); + + // Returns the number of instructions. + uint instructions(); + + // Returns the last instruction + sp_instr *last_instruction(); + + // Resets lex in 'thd' and keeps a copy of the old one. + void reset_lex(THD *); + + // Restores lex in 'thd' from our copy, but keeps some status from the + // one in 'thd', like ptr, tables, fields, etc. + void restore_lex(THD *); + + // Put the instruction on the backpatch list, associated with + // the label. + void push_backpatch(sp_instr *, struct sp_label *); + + // Update all instruction with this label in the backpatch list to + // the current position. + void backpatch(struct sp_label *); + + // Returns the SP name (with optional length in '*lenp'). + char *name(uint *lenp = 0); + + // Returns the result type for a function + Item_result result(); + + // Sets various attributes + void sp_set_info(char *creator, uint creatorlen, + longlong created, longlong modified, + bool suid, char *comment, uint commentlen); + } + + + - Instructions + + - The base class: + class sp_instr + { + // 'ip' is the index of this instruction + sp_instr(uint ip); + + // Execute this instrution. + // '*nextp' will be set to the index of the next instruction + // to execute. (For most instruction this will be the + // instruction following this one.) + // Returns 0 on success, non-zero if some error occured. + virtual int execute(THD *, uint *nextp) + } + + - Statement instruction: + class sp_instr_stmt : public sp_instr + { + sp_instr_stmt(uint ip); + + int execute(THD *, uint *nextp); + + // Set the statement's Lex + void set_lex(LEX *); + + // Return the statement's Lex + LEX *get_lex(); + } + + - SET instruction: + class sp_instr_set : public sp_instr + { + // 'offset' is the variable's frame offset, 'val' the value, + // and 'type' the variable type. + sp_instr_set(uint ip, + uint offset, Item *val, enum enum_field_types type); + + int execute(THD *, uint *nextp); + } + + - Unconditional jump + class sp_instr_jump : public sp_instr + { + // No destination, must be set. + sp_instr_jump(uint ip); + + // 'dest' is the destination instruction index. + sp_instr_jump(uint ip, uint dest); + + int execute(THD *, uint *nextp); + + // Set the destination instruction 'dest'. + void set_destination(uint dest); + } + + - Conditional jump + class sp_instr_jump_if_not : public sp_instr_jump + { + // Jump if 'i' evaluates to false. Destination not set yet. + sp_instr_jump_if_not(uint ip, Item *i); + + // Jump to 'dest' if 'i' evaluates to false. + sp_instr_jump_if_not(uint ip, Item *i, uint dest) + + int execute(THD *, uint *nextp); + } + + - Return a function value + class sp_instr_freturn : public sp_instr + { + // Return the value 'val' + sp_instr_freturn(uint ip, Item *val, enum enum_field_types type); + + int execute(THD *thd, uint *nextp); + } + + - Push a handler and jump + class sp_instr_hpush_jump : public sp_instr_jump + { + // Push handler of type 'htype', with current frame size 'fp' + sp_instr_hpush_jump(uint ip, int htype, uint fp); + + int execute(THD *thd, uint *nextp); + + // Add condition for this handler + void add_condition(struct sp_cond_type *cond); + } + + - Pops handlers + class sp_instr_hpop : public sp_instr + { + // Pop 'count' handlers + sp_instr_hpop(uint ip, uint count); + + int execute(THD *thd, uint *nextp); + } + + - Return from a CONTINUE handler + class sp_instr_hreturn : public sp_instr + { + // Return from handler, and restore variables to 'fp'. + sp_instr_hreturn(uint ip, uint fp); + + int execute(THD *thd, uint *nextp); + } + + - Push a CURSOR + class sp_instr_cpush : public sp_instr_stmt + { + // Push a cursor for statement 'lex' + sp_instr_cpush(uint ip, LEX *lex) + + int execute(THD *thd, uint *nextp); + } + + - Pop CURSORs + class sp_instr_cpop : public sp_instr_stmt + { + // Pop 'count' cursors + sp_instr_cpop(uint ip, uint count) + + int execute(THD *thd, uint *nextp); + } + + - Open a CURSOR + class sp_instr_copen : public sp_instr_stmt + { + // Open the 'c'th cursor + sp_instr_copen(uint ip, uint c); + + int execute(THD *thd, uint *nextp); + } + + - Close a CURSOR + class sp_instr_cclose : public sp_instr + { + // Close the 'c'th cursor + sp_instr_cclose(uint ip, uint c); + + int execute(THD *thd, uint *nextp); + } + + - Fetch a row with CURSOR + class sp_instr_cfetch : public sp_instr + { + // Fetch next with the 'c'th cursor + sp_instr_cfetch(uint ip, uint c); + + int execute(THD *thd, uint *nextp); + + // Add a target variable for the fetch + void add_to_varlist(struct sp_pvar *var); + } + + + - Utility functions: sp.h + + #define SP_OK 0 + #define SP_KEY_NOT_FOUND -1 + #define SP_OPEN_TABLE_FAILED -2 + #define SP_WRITE_ROW_FAILED -3 + #define SP_DELETE_ROW_FAILED -4 + #define SP_GET_FIELD_FAILED -5 + #define SP_PARSE_ERROR -6 + + // Finds a stored procedure given its name. Returns NULL if not found. + sp_head *sp_find_procedure(THD *, LEX_STRING *name); + + // Store the procedure 'name' in the database. 'def' is the complete + // definition string ("create procedure ..."). + int sp_create_procedure(THD *, + char *name, uint namelen, + char *def, uint deflen, + char *comment, uint commentlen, bool suid); + + // Drop the procedure 'name' from the database. + int sp_drop_procedure(THD *, char *name, uint namelen); + + // Finds a stored function given its name. Returns NULL if not found. + sp_head *sp_find_function(THD *, LEX_STRING *name); + + // Store the function 'name' in the database. 'def' is the complete + // definition string ("create function ..."). + int sp_create_function(THD *, + char *name, uint namelen, + char *def, uint deflen, + char *comment, uint commentlen, bool suid); + + // Drop the function 'name' from the database. + int sp_drop_function(THD *, char *name, uint namelen); + + + - The cache: sp_cache.h + + /* Initialize the SP caching once at startup */ + void sp_cache_init(); + + /* Clear the cache *cp and set *cp to NULL */ + void sp_cache_clear(sp_cache **cp); + + /* Insert an SP to cache. If **cp points to NULL, it's set to a + new cache */ + void sp_cache_insert(sp_cache **cp, sp_head *sp); + + /* Lookup an SP in cache */ + sp_head *sp_cache_lookup(sp_cache **cp, char *name, uint namelen); + + /* Remove an SP from cache */ + void sp_cache_remove(sp_cache **cp, sp_head *sp); + + + - The mysql.proc schema: + + CREATE TABLE proc ( + db char(64) binary DEFAULT '' NOT NULL, + name char(64) DEFAULT '' NOT NULL, + type enum('FUNCTION','PROCEDURE') NOT NULL, + specific_name char(64) DEFAULT '' NOT NULL, + language enum('SQL') DEFAULT 'SQL' NOT NULL, + sql_data_access enum('CONTAINS_SQL') DEFAULT 'CONTAINS_SQL' NOT NULL, + is_deterministic enum('YES','NO') DEFAULT 'NO' NOT NULL, + security_type enum('INVOKER','DEFINER') DEFAULT 'DEFINER' NOT NULL, + param_list blob DEFAULT '' NOT NULL, + returns char(64) DEFAULT '' NOT NULL, + body blob DEFAULT '' NOT NULL, + definer char(77) binary DEFAULT '' NOT NULL, + created timestamp, + modified timestamp, + sql_mode set( + 'REAL_AS_FLOAT', + 'PIPES_AS_CONCAT', + 'ANSI_QUOTES', + 'IGNORE_SPACE', + 'NOT_USED', + 'ONLY_FULL_GROUP_BY', + 'NO_UNSIGNED_SUBTRACTION', + 'NO_DIR_IN_CREATE', + 'POSTGRESQL', + 'ORACLE', + 'MSSQL', + 'DB2', + 'MAXDB', + 'NO_KEY_OPTIONS', + 'NO_TABLE_OPTIONS', + 'NO_FIELD_OPTIONS', + 'MYSQL323', + 'MYSQL40', + 'ANSI', + 'NO_AUTO_VALUE_ON_ZERO' + ) DEFAULT 0 NOT NULL, + comment char(64) binary DEFAULT '' NOT NULL, + PRIMARY KEY (db,name,type) + ) comment='Stored Procedures'; + + -- +
\ No newline at end of file diff --git a/Docs/sp-implemented.txt b/Docs/sp-implemented.txt new file mode 100644 index 00000000000..6f2cf49b3b0 --- /dev/null +++ b/Docs/sp-implemented.txt @@ -0,0 +1,112 @@ +Stored Procedures implemented 2004-01-29: + + +Summary of what's implemented: + + - SQL PROCEDUREs/FUNCTIONs (CREATE/DROP) + - CALL + - DECLARE of local variables + - BEGIN/END, SET, CASE, IF, LOOP, WHILE, REPEAT, ITERATE, LEAVE + - SELECT INTO local variables + - "Non-query" FUNCTIONs only + - Prepared SP caching + - CONDITIONs and HANDLERs + - Simple read-only CURSORs. + - SHOW CREATE PROCEDURE/FUNCTION and SHOW PROCEDURE/FUNCTION STATUS + + +Summary of Not Yet Implemented: + + - SQL statements using tables (like SELECT, INSERT, UPDATE etc) in FUNCTIONs + - External languages + - Access control + - SQL-99 COMMIT (related to BEGIN/END) + - FOR-loops + - CASCADE/RESTRICT for ALTER and DROP + - ALTER/DROP METHOD (as it implies User Defined Types) + - SIGNAL and RESIGNAL, and UNDO handlers + + +List of what's implemented: + + - CREATE PROCEDURE|FUNCTION name ( args ) characteristics body + where characteristics is: + LANGUAGE SQL | + [NOT] DETERMINISTIC | + SQL SECURITY [DEFINER|INVOKER] | + COMMENT string + However the DETERMINISTIC setting is not currently used. + + - ALTER PROCEDURE|FUNCTION name characteristics + CASCADE/RESTRICT is not implemented. + characteristics is: + COMMENT string | + SQL SECURITY [DEFINER|INVOKER] | + NAME newname + + - DROP PROCEDURE|FUNCTION [IF EXISTS] name + CASCADE/RESTRICT is not implemented. + + - CALL name (args) + OUT and INOUT parameters are also works for user variables ("global" + variables) - i.e., if a procedure is defined as: + CREATE PROCEDURE foo(OUT p INT) ...; + a call like: + CALL foo(@x); + will set @x to the output value. + + - Function/Procedure body: + - BEGIN/END + Is parsed, but not the real thing with (optional) transaction + control, it only serves as block syntax for multiple statements (and + local variable binding). + Note: Multiple statements requires a client that can send bodies + containing ";". This is handled in the CLI clients mysql and + mysqltest with the "delimiter" command. Changing the end-of-query + delimiter ";" to for instance "|" allows ";" to be used in the + routine body. + - SET of local variables + Implemented as part of the pre-existing SET syntax. This allows an + extended syntax of "SET a=x, b=y, ..." where different variable types + (SP local and global) can be mixed. This also allows combinations + of local variables and some options that only make sense for + global/system variables; in that case the options are accepted but + ignored. + - The flow control constructs: CASE, IF, LOOP, WHILE, ITERATE and LEAVE + are fully implemented. + - SELECT ... INTO local variables (as well as global session variables) + is implemented. (Note: This is not SQL-99 feature, but common in other + databases.) + - A FUNCTION can have flow control contructs, but must not contain + an SQL query/statement, like SELECT, INSERT, UPDATE, etc. The reason + is that it's hard to allow this is that a FUNCTION is executed as part + of another query (unlike a PROCEDURE, which is called as a statement). + The table locking scheme used makes it difficult to allow "subqueries" + during FUNCTION invokation. + - SPs are cached, but with a separate cache for each thread (THD). + There are still quite a few non-reentrant constructs in the lexical + context which makes sharing prepared SPs impossible. And, even when + this is resolved, it's not necessarily the case that it will be faster + than a cache per thread. A global cache requires locks, which might + become a bottleneck. (It would save memory though.) + - CONDITIONs and HANDLERs are implemented, but not the SIGNAL and + RESIGNAL statements. (It's unclear if these can be implemented.) + The semantics of CONDITIONs is expanded to allow catching MySQL error + codes as well. UNDO handlers are not implemented (since we don't have + SQL-99 style transaction control yet). + - Simple read-only CURSORs are implemented, but not yet any of the + optional arguments to DECLARE (SCROLL, SENSITIVE, etc) or FETCH + (NEXT, PRIOR, etc). Cursors are ASENSITIVE, READ-ONLY, non-SCROLLing. + (The additional syntax will be added for completeness, but for the + most part unsupported with the current underlying cursor mechanism.) + N.B. The current implementation is temporary and only works within a + stored procedure, and may not perform well for very large result sets. + A "real" cursor implementation is under development; this will replace + the current one when it's finished. + + - SHOW procedures and functions + SHOW CREATE PROCEDURE|FUNCTION <name> + returns the definition of a routine. + SHOW PROCEDURE|FUNCTION STATUS [LIKE <pattern>] + returns characteristics of routines, like the name, type, creator, + creation and modification dates, etc. |