Friday, February 23, 2007

Cluster Certification and the MySQL User's Conference

So far, I've never blogged about the work I've been doing for the MySQL Certification Team. Time to change that: I'd like to tell you a little bit more about the things I've been doing since I joined MySQL AB.

I started in July 2006 and from the on, I've been almost exclusively occupied with activities to develop certification for MySQL 5.1 Cluster.
A summary of the things I have done so far:

  • Working with the training department to design an outline for the cluster exam

  • Interviewing MySQL Cluster developers to check all kinds of facts and details of the behaviour of MySQL 5.1 Cluster

  • Creating, reviewing and editing numerous items for the cluster exam

  • Working with the documentation department to align the exam with the development of a MySQL 5.1 Cluster Certification Study Guide

  • Reviewing chapters and exercises for the MySQL 5.1 Cluster Certification Study Guide


Cluster Certification and the MySQL User's Conference


I'm also very excited to be speaking about MySQL Cluster Certification at the MySQL User Conference and Expo (April 23 through 26, in Santa Clara, CA).



I'll be giving a MySQL Cluster Certification Primer on the Monday morning. Kai Voigt, who was an enormous help during the development of the cluster exam, will be giving a MySQL 5.0 DBA I Certification Primer, and Sarah Sproehnle will be giving the MySQL 5.0 DBA II Certification Primer.

If you like, you can get certified at the conference. At the UC, Certification exams cost 25 US$. That's just a little more than a tenth of normal price (200 US$)! I will be around to proctor various MySQL Certification Exams, including the MySQL 5.1. Certified Cluster DBA Exam.

I'll be writing some more about the new MySQL Cluster Certification and the corresponding study guide in a few days. In the mean time, check out the Cluster Section in the MySQL Certification Candidate Guide.

See you at the UC!

Tuesday, February 20, 2007

Updated procedure for creating MySQL FEDERATED tables

Three months ago, I wrote about a procedure to create MySQL FEDERATED tables.

I just added a quick fix that should make the procedure less susceptible to issues relating to bug #23856. To do that, I had to remove the ORDER BY ordinal_position bits in all the calls to GROUP_CONCAT over the rows in the information_schema.COLUMNS table.

If you include the ORDER BY ordinal_position clause, the concatenation result will mess up sometimes. The behaviour can be attenuated somewhat by decreasing value for the group_concat_max_len server variable, but so far, I have not seen a sufficiently large valy of group_concat_max_len that does not display this behaviour.

Omitting the ORDER BY ordinal_position clause does not seem change the order. Rows from information_schema.COLUMNS seem to be ordered by TABLE_SCHEMA, TABLE_NAME, COLUMN and ORDINAL_POSITION anyhow. I hope I can rely on that order, as all sorts of trouble are to be expected when the column order of the local table differs from that of the remote table.

I also added some output so you can see what the procedure is doing. Some steps, like getting the remote metadata, take rather long, and I found some output to be helpful.

The updated procedure can be found here in the usual spot at MySQLForge.

The updated procedure does not yet support creating federated tables using a separate SERVER schema object (see the manual).

Please, try it, and report bugs in the procedure by adding a comment to this blog entry. Thank you!

Sunday, February 11, 2007

MySQL: Transactions and Autocommit

Some people believe that the ability to commit or rollback multiple statements as a single unit is a defining characteristic of the concept of transactions. They draw the -false- conclusion that enabling autocommit is the same as disabling transactions. It is easy to demonstrate why this is not true.

Autocommit


With autocommit enabled, every statement is wrapped within its own transaction. Successful execution of a statement is implicitly followed by a COMMIT, and the occurrence of an error aborts the transaction, rolling back any changes.

By default, autocommit is enabled in MySQL. You can check the current setting by executing the following statement:
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)

The @@ prefix denotes a server variable. The 1 means that autocommit is currently enabled. Let's leave it enabled so we can test the difference with not using transactions.

Transactions and Storage Engines


First, we create a table backed by a storage engine that is capable of handling transactions. I'm using the new FALCON engine in these examples, but you can try it with any of the storage engines that is capable of handling tranactions, such as InnoDB, ndbcluster, PBXT or SolidDB. If you want to try with falcon, please download and install MySQL 5.2.

Let's create that FALCON table in the test database:
mysql> use test;
Database changed
mysql> create table test_falcon(i int primary key)
-> engine = falcon
-> ;
Query OK, 0 rows affected (0.70 sec)

A word of warning: be sure to check the message after creating the table. If you accidentally type the name of the storage engine wrong, a table might be created anyway. If you see a warning after creating the table, always check the warning:
mysql> create table t(i int) engine = ferrari;
Query OK, 0 rows affected, 1 warning (0.14 sec)

mysql> show warnings;
+-------+------+--------------------------------+
| Level | Code | Message |
+-------+------+--------------------------------+
| Error | 1286 | Unknown table engine 'ferrari' |
+-------+------+--------------------------------+
1 row in set (0.00 sec)

You can completely avoid this problem by including NO_ENGINE_SUBSTITUTION in the SQL_MODE. (In fact, I recommend to include it and make sure the proper mode is set when starting the server by including it in the [mysqld] section of your my.ini or my.cnf option file)

Testing Atomicity


Now, we execute a single statement that inserts two rows of data into the table:
mysql> insert into test_falcon values (1),(2);
Query OK, 2 rows affected (0.05 sec)
Records: 2 Duplicates: 0 Warnings: 0

The query executed succesfully, so we can assume two rows are now stored in the table. We can of course attempt to roll back to undo these changes:
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

Of course, when we check, the rows are still there:
mysql> select * from test_falcon;
+---+
| i |
+---+
| 1 |
| 2 |
+---+
2 rows in set (0.02 sec)

This is completely normal and expected behaviour. The rows were automatically committed right after the insert statement was executed: that is the meaning of autocommit.

We will now attempt to add three more rows to the table:
mysql> insert into test_falcon values (3),(4),(1);
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'

Whoops! This statement did not execute succesfully. We attempted to insert three rows, but at least one of them would violate the PRIMARY KEY constraint if it would be inserted. So at least, the violating row is rejected. But what about the other rows? Let's find out:
mysql> select * from test_falcon;
+---+
| i |
+---+
| 1 |
| 2 |
+---+
2 rows in set (0.00 sec)

Aha! The statement did not execute successfully, and therefore, all changes that would result from executing that statement are undone. With a posh word, we can say that the statement was executed atomically. The property of transactions that allows for atomic execution of statements is called atomicity

To say that the statement was executed atomically just means that the entire statement, and all of the changes it would achieve is executed as one single unit, undividable. If it succeeds, all associated changes on all rows it touches are made permanent. If it fails, none of the changes are made.

So, Atomicity ensures that statement execution is always complete: It either completely succeeds, or it completely fails. How does this compare to not using transactions at all? Well, we can test this with the MyISAM storage engine. The MyISAM storage engine does not implement transactions, and has no understanding of the autocommit setting. It is completely ignorant of it, because it can't commit or rollback anyway.

So, let's create a table again, this time using the MyISAM storage engine:
mysql> create table test_myisam (i int primary key)
-> engine = myisam
-> ;
Query OK, 0 rows affected (0.00 sec)

Again, we insert two rows in a single statement.
mysql> insert into test_myisam values (1),(2);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0

Now for the interesting bit. Again, we execute one statement in order to insert multiple rows, and again, we deliberately construct the statement in a manner to violate the primary key constraint:
mysql> insert into test_myisam values (3),(4),(1),(5);
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'

Like with the test_falcon table, this statement is not acceptable, because there is again at least one row that would violate the PRIMARY KEY constraint if it would be inserted. So, how is this different from the previous test? Let's find out by looking what's in the table...
mysql> select * from test_myisam;
+---+
| i |
+---+
| 1 |
| 2 |
| 3 |
| 4 |
+---+
4 rows in set (0.00 sec)

Boom! The last statement attempted to insert no less than four rows, yet it added only two! Why only two? Well, after inserting two rows (3 and 4) succefully, a row was encountered that would violate the primary key constraint if it would be inserted (the value 1 was already present). At that point, an error condition was raised, and execution stopped. The remainder of the rows (5) was not even processed.

It's impossible give a clear answer to the question whether the statement succeeded or failed. We ordered four rows to be inserted, yet it failed to insert two of them. Clearly, the statement did not succeed completely. It did not fail completely either, because it succeeded in inserting two of the four rows.

This indeterminate state is the consequence of the lack of atomicity. The MyISAM engine does not support atomic execution of statements. The statement may succeed for some of the rows, and fail for others. This is simply impossible for the transactional engines. Statements on transactional statements are always executed atomically, regardless of the setting of autocommit.

Disabling Autocommit


So, I hope it's clear now why autocommit has everything to do with transactions. Still for a lot of people, the added value for using transactions is the ability to execute multiple statements within a single transactions. To do that, one needs to disable autocommit.

Within an application's session it is pretty easy to disable autocommit. One just needs to execute the proper SET statement:
SET autocommit=0

However, a lot of people are not really satisfied with this solution. Especially those that are used to Oracle, is seems daft to have to explicitly specify it.

There is a trick one can use to automatically disable autocommit for each newly created session. In the my.ini or my.cnf configuration file, you can specify the init_connect option. This option can be used to automatically execute a statement or sequence of statements at the beginning of each new session.
[mysqld]
init_connect='SET autocommit=0'

It's documented in the manual, and the example happens to use autocommit. Take heed though: the statements specified for the init_connect option are not executed for users that have the SUPER privilege.

Monday, January 22, 2007

GCC: -march and -mtune

Some might've noticed that the check-cpu script in the MySQL BUILD directory does not always succeed in detecting the right processor:

roland@roland-laptop:/opt/mysql/5.1/bitkeeper/BUILD$ ./check-cpu
BUILD/check-cpu: Oops, could not find out what kind of cpu this machine is using.

When you dig a little deeper into that script, you will notice that the name of you particular processor (in my case: Intel(R) Core(TM)2 CPU T5600 @ 1.83GHz) is indeed not there and thus not checked. Finally you may find that the model and name of your processor are passed to the gcc compiler.

As far as I cans see the information is used to set the values of the --mtune and --march compiler options. What those are? Hehe! Ok, here's a partial result from man gcc:

-march=cpu-type: Generate instructions for the machine type cpu-type. The choices for cpu-type are the same as for -mtune. Moreover, specifying -march=cpu-type implies -mtune=cpu-type.

Mmm, not sure why -mtune=cpu-type is there in the first place then...

Anybody?

(PS: use /proc/cpuinfo to find out what kind of processor you really have....)

Compiling MySQL From Source on Linux: Check your Tools

I hear a lot of people say: "Well, compiling MySQL from source might pose a problem to Windows users, but it's straight forward for Linux users." Quite often, the difference is explained by claiming that Linux users are used to doing things themselves, and gaining an extra bit of customization by doing so.

My impression is that a large part of the people that say this, are already quite experienced Linux users that are quite comfortable with compiling software from source. The entire toolchain that is involved is already set up on their system, and some of them probably already forgot what they had to do in order for it to make it work.

What is quite often not mentioned is that the required toolchain does not magically land on your system. If you are experiencing prolems compiling MySQL source it's quite likely the toolchain is broken somewhere.

Those that want to compile MySQL from source on Linux: please take some time to read some of the excellent advise from Jay Pipes. Although rather inconspicously titled Making a Corresponding Test Case for your Patch, his HOWTO contains very important information to set up your building environment.

For those that can't wait:

...here are the things you should have installed to avoid headaches:

* automake version 1.9.6
* autoconf version 2.60
* bison version 2.3
* m4 version 1.4.4
* libtool version 1.5.22
...
Note that it is important what order you install these tools! libtool must be installed last to avoid linking with older versions of the other tools. If you don't, you get weird errors.

I'm running Kubuntu Edgy, and I could get packages for all of these things, except one, Bison. I had to compile that one from source too.

Tuesday, January 09, 2007

Oracle SQL Developer 1.1 Supports MySQL

Being quite tied up in my daily job, I totally missed the that Oracle has been developing a database application development tool, SQL Developer. In fact, this tool was previously known as "project Raptor" and has now matured to version 1.1. The -to me- interesting news is that:

  • It's free of charge.

  • It runs on Linux, Mac OS/X and Windows

  • Besides the Oracle database, it also supports other databases, including MySQL


Ok - I know all free software adepts will probably have left by now for three reasons, being that the title of this blog entry contains "Oracle", the tool is "free as in beer" and of course because the tool runs on Microsoft Windows. But let's not be distracted. Let's just take a few moments and see how to set up this tool to work with MySQL to figure out how this tool can be useful to us. I read that some people are actually using it to work with MySQL databases, so why not give it a try.

I had a quick go and installed the tool on the family Windows XP desktop. For a few sad reasons, I have been using that as my work PC, because the laptop I especially purchased to work on is being repaired at this moment. I might revisit this topic and install this tool on Linux too. It should not be too much of a problem.

Anyway - what do we have to do to get this tool up and running? Here goes:

Download and License


First of all, if you're going to download anything at all from Oracle, you need to sign in using your otn account. You'll find out soon enough because you'll be prompted to sign in. I set up my account years ago, and I can't really remember the details. I'm pretty sure you need at least an e-mail address. I have never received unsollicited emails from Oracle. It's probably due to me not checking or unchecking checkboxes in the account form, but I just want to put you at ease and let you know that the account does not mean that you will be receiving spam.

You can download the tool free of charge from the following location: http://www.oracle.com/technology/software/products/sql/index.html. You will have to accept the license agreement though - this can be done by selecting the radiobutton the very top of the page.

The license is particularly interesting for those that plan to use this tool in a production environment. To a large extent, the content is what I'd expect for a typical "free as in beer" product. I have no principal objections to using software without being able to modify it, and I certainly have no intention of removing any of the logos or whatever. From a practical point of view, it becomes harder that you can't freely distribute the software to third parties, but still does not have to pose a problem if you plan to use this software only for you own business. Or does it?

The programs may be installed on one computer only, and used by one person in the operating environment identified by us.
...
[You may not]...assign this agreement or give or transfer the programs or an interest in them to another individual or entity
...
[You may not]...disclose results of any program benchmark tests without our prior consent

I think that a literal interpretation of these stanzas from the license agreement sums up the major impediments. If my interpretation is correct, it means that the license agreement is between Oracle and an individual, meaning that each developer in your team must download it's own copy. Each individual has to sign the agreement individually too. You cannot dowload one copy and put it on your central file server - not even if the distribution is confined to only your own business. I hope that me blogging about the tool is not interpreted as benchmarking, because that would mean I'm violating the agreement.

Assuming you're willing to accept the license agreement, you should choose the download that's most appropriate for you. Dowloads are available in the following formats:

  • .zip file including JDK 1.5, 65.2Mb. This is listed as the "Windows" distribution on the oracle site.

  • .zip file without JDK, 38.4Mb if you have a JDK 1.5 (Update 6) installed already. This one is listed twice on the Oracle download site: once as a "Windows" distribution, once as a "Multiple Platforms" distribution. This probably means that this distribution will run on any Windows or *nix system that has some sort of JDK 1.5 installed.

  • .tar.gz file for Mac OS/X, 38.3Mb. This also requires you to separately install J2SE 5.0 (Release 3)

  • .rpm file for Linux, 38.4Mb. This also requires you to separately install JDK 1.5 (Update 6)


Installation and Configuration


I chose to download the second option - the "Multiple Platforms" distribution. I already had a JDK 1.5 installed so this seemed to be most appropriate for me. This yields a single zip file, sqldeveloper-2364-no-jre.zip which you have to unzip after downloading it. It unzips a sqldeveloper directory wich contains all the software.

Inside the sqldeveloper directory, you'll find a sqldeveloper.exe file and a sqldeveloper.sh file. Windows users double-click the .exe, and although I haven't tried if it works, my bet is that *nix users need to run the .sh bash script. There mey be some hidden magic to discover the java environment on your machine. However, I got a dialog which required me to browse for one:

sql-dev-jdk

You must take care to specify the java executable from a java development kit (JDK) - a java runtime environment (JRE) is not sufficient. I tried it, and got this error message:

sql-developer-jre

The download page is pretty clear about it, but because the .zip file name contains no-jre I figured I'd give it a try anyway. So to avoid that problem, make sure you download and install JDK 1.5 (or a compatible implementation) and specify the java executable located in {JDK 1.5 home}/bin.

sql-developer-splash

After correctly specifying the java program in the JDK, sqldeveloper will start. It will pop up the splashscreen, and ask you if you want to migrate your settings froma previous installation. You won't have to specify the location of java hereafter. That is, unless you upgrade your JDK - you might want to respecify it.

Unfortunately, I haven't found out yet where sqldeveloper stores the information. I noticed a jdk.conf file in the sqldeveloper\jdev\bin directory, but that appears to be there for that other Oracle IDE, JDeveloper. At least, I don't see anything being written there so it's probably not what you'd be looking for.

Post-installation tasks


In order to connect a MySQL database, you will need the MySQL jdbc driver, Connector/J. You can download it here.

Download the archive and unpack it at a location you feel is convenient. We then have to tell the SQL Developer tool about it too. To do that, navigate to the "Preferences..." item in the "Tools" menu.

sql-developer-preferences

This will open the Preferences dialog. In the treeview on the left side of the dialog, expand the "Databases" node, and activate the "Third Party JDBC Drivers" item beneath it.

sql-developer-jdbc

Use the "Add Entry" button to open a file browser. From there, locate the MySQL Connector/J .jar file:

sql-developer-jdbc-2

This is a one time action too . You won't have to repeat it, unless you upgrade Connector/J. On Linux, I just make a symbolic link to the most recent .jar file, and use instead of the actual .jar file. That way, you never have to worry about upgrades, because you simply re-wire the symbolic link to the newer .jar file. Alas, windows users are not so lucky and have to put up with manually reconfiguring their programs to use the newer driver.

Connecting to MySQL


Now we're ready to actually make a new connection. To do that, make sure you have the "Connections" toolpane activated. By default, it's on the left and active but you can drag and drop the different toolpanes almost everywhere, or you might accidentally have closed it. If you're in doubt, explicitly activate it using the "View" menu:

sql-developer-view

This should pop up and activate the "Connections" toolpane. Use the mouse to right-click the "Connections" node in the treeview on the "Connections" toolpane to create a new connection. A menu pops up. Although you might be tempted to think that you need the "New" item, you really need the "New Connection" item:

sql-developer-new-connection

This pops up a dialog where you can specify the connection details. For MySQL, be sure to select the "MySQL" tab. You will see pretty much all the fields that are required to set up a connection:

sql-developer-new-connection-mysql

The "Connection Name" field is just a local name for the connection for use within SQL Developer. The "Username" and "Password" correspond to the credentials corresponding to the database account. In the "MySQL" Tab, you can specify the hostname and port - all the usual data.

On the bottom of the dialog, there is a "Test" button, which is supposedly there in order to test the connection. It does pop up a small dialog, but immediately, it disappears, so I don't really know what it is telling me. I suspect this is by design, because you can see whether the tes was successfull: after hitting the test button, a status message will appear immediately above the Help button. The message will be shown in red if the test does not succeed:

sql-developer-test-connection

If you want to, you can select a particular database, but you are not required to do so. If you want to, simply hit the "Choose Database" button, and use the combobox to select one of the available databases:

sql-developer-new-connection-choose-database

I find it a bit dissapointing that there seems to be no way to specify custom properties for the connection. There is a whole bunch of extra configuration properties implemented by the MySQL Connector/J, but there is no interface to specify them. Maybe this will be solved in a newer version of SQL Developer.

Using SQL Developer with MySQL


I played a bit with SQL developer, and so far, I think that the MySQL Support is rather limited. The tool lets you browse the database and execute SQL statements, but that's about it.

A few things I would expect to be implemented:

Database selection


In the connection dialog, we had an option to select a particular database. Surprisingly, all databases pop up anyway in the treeview. Of coure, if you don't specify a particular database, all databases pop up too, which I would expect. In that case however, I would expect that selecting a particular database would also USE that database. This is not the case: SQL statements will fail unless all identifiers are properly qualified. On the following screenshot, you might notice that the test database was selected, but under the hood, this does not USE the database.

sql-developer-default-database

Maybe all this can all be explained because of the different semantics between Oracle and MySQL. As you can see, all MySQL Databases or schemata (a synonymous term in MySQL) appear under the connection. But the icon used to adorn them, is that of a little puppet - a user. This is probably because in Oracle, a schema is not really a separate kind of thing - rather, it is the collection of all objects owned by a particular user, and a database is a container for these schemata.

Schema object editor


One of the first things I tried, was to create a new table. It seemed most reasonable to expand the node corresponding to my "test" database, to right-click the "Tables" node. As expected, a menu pops up, but surprisingly, there is no option to create a new table.

sql-developer-new-table

I also tried the marked toolbar buttons as these suggested to be meant to create a new object, but again no luck. I'm pretty sure the functionality is supported for Oracle databases, because the integrated help files do mention dialogs to edit schema objects.

When you try to edit an existing table, a set of tabpages appear that show the structure of the table.

sql-developer-edit-table

However, in that case too, there is no functionality to modify or alter the table design. The status bar does suggest a real edit mode exists, but I can't seem to unlock or activate it. Assuming that it does work for Oracle databases, it's introspection only for MySQL. Creating and editing tables can be done only through DDL.

Working with Data


It seems that working with table data is largely confined to the same limitations as working with schema objects: existing data is displayed, and you can sort it, but that's as far as it goes.

There is no interface to insert new rows in an existing table; no interface to delete rows. You can move your cursor to a field, and click. A blinking cursor appears in the field, and suggests you can type a new value: you can't. Surprisingly, double-clicking on a field in an existing row does pop up an editor dialog - however, it does not let you modify the value. As you can see, the Ok button is disabled.

sql-developer-edit-data

This puzzles me enormously. I mean, I can imagine why the developers waited a little before implementing full blown schema editors: I think it's safe to say that DDL is the part of SQL that has most differences between the various dialects. It's quite understandable too, because creating tables involves a specification of datatypes (which tend to differ between SQL dialects) and all kinds of physical properties, such as storage options.

However, once the schema objects are created, the SQL for working with the data is fairly similar for all SQL dialects alike. Ok, there are of course differences, mainly for the built-in functions, but once you have jdbc access, it should not be hard to provide a generic table data editor that works regardless of the underlying SQL dialect. However, it's not in SQL developer at present - at least, not for MySQL.

No resultset for CALL, EXECUTE and SHOW


SQL Developer does not display the resultset(s) returned by SHOW, EXECUTE and CALL statements. This is particularly annoying for CALL, because it makes it pretty hard to debug stored procedures.

An 'orphan' SELECT statement in a MySQL stored procedure will return a resultset to the calling client. Although some procedures return a resultset as part of their intended operation, I find this feature particularly useful for debugging purposes. With SQL Developer, there is no way to see the data returned by those statements, which means that debugging can only be done by setting up a separate table to log the debugging messages.

SET syntax


I mentioned that it is currently not possible to set all kinds of jdbc driver options when configuring a MySQL connection. Luckily, a lot of the options you'd want to set for the driver can normally be controlled also through the MySQL SET syntax. Alas - not with SQL Developer.

sql-developer-set

The SET keyword happens to identify a command that is implemented by Oracle's SQL*Plus command line query tool. Apparently, there is some logic between the SQL command editor in SQL Developer and the jdbc driver which senses that the SET command is not to be sent to the server, and hence it is skipped.

I can see the usefulness of not letting SQL*Plus commands through to an Oracle server: it makes it possible to use SQL Developer to run legacy SQL*Plus scripts. However, the current implementation blocks a part of the commands in MySQL's SQL dialect. I know how all kinds of people will tell me that you should never use SET commands anyway because they are non-standard and proprietary and all that - fact is that sometimes, you simply need these commands to get some work done. Whatever work that is, you won't get it done with SQL Developer, because it decides to skip these commands.

I think this could easily have been avoided too. The SQL Developer tool can detect which driver it is talking too. That should enable it to block the SET commands from being sent to Oracle servers, and simply allow them to pass through to non-Oracle servers. I know MS SQL Server implements the SET command too - MS SQL Server users will encounter similar problems.

Stored Procedure Parameters


I really don't know how long I can keep this up - my guess is, too long. Therefore, one more to wrap it up.

SQL Developer does not provide a stored procedure editor, wich is rather unsurprising for a tool that does not provide a table editor. However, we can just resign to using DDL statements - a stored procedure editor would not add that much value anyway.

sql-developer-parameters

It would be nice to be able to at least retrieve the stored procedure code using SQL Developer, right? No such luck. When you use the treeview to browse for you procedure, you can retrieve the code. Alas, the parameters are missing from the code. They're just not there:

sql-developer-parameters2

I suspect this is because SQL Developer tries to use the information_schema.PARAMETERS view, which is not implemented in MySQL. Of course, I hope MySQL will eventually implement that, but even then - there is a perfectly good solution to this problem: the SHOW CREATE PROCEDURE command. Of course, this is MySQL proprietary syntax etc, but the point is, if this tool is to support older MySQL releases, it will need to use that method in order to be able to offer the user the possibility to edit stored procedures.

Now what?


Good question. I think it's very encouraging to see Oracle adding connectivity for other database products to their tool. I suspect I would like to use this tool too, provided that it would actually support MySQL Databases at least half as good as it supposedly does so for Oracle Databases.

Frankly, at this point, I wouldn't exactly say SQL Developer supports MySQL. It allows you to connect to it and to issue some queries against it. I can imagine that some people like the ability to use a graphical user interface to browse the database too. But I can't help but conclude that in it's present form, SQL Developer cannot be used for any serious MySQL Application development. The graphical user interface does just not provide enough functionality to perform even the most basic tasks, and even as a Query tool it's lacking too much functionality.

I really hope that what we're seeing now is just the result of a premature release of this functionality. Also, to the best of my knowledge, it's quite uncommon for Oracle to develop tools that even try to support other database products than their own, so I guess we should have a little patience before dismissing this tool alltogether. I will keep an eye on this tool, but my guess it will take at least a year before we'll see it mature enough to be seriously usable.

Is there nothing good to say about SQL Developer in it's present form? Well, so far, it seems to be pretty stable. I found it to be reasonable fast too once it's up and running. As far as it goes, the GUI is pretty intuitive too. But except for the stability, these are all minor pro's compared to the major con's in my opinion. Let's hope it gets better.

Saturday, December 09, 2006

MySQL stored routines and the command line client: No comment...Or Maybe..?!

A popular myth holds that the MySQL Database Server strips the comments from stored procedures, functions, triggers, views and events. This is not true. Or at least, it is only somewhat true.

On an earlier occasion, I have written about MySQL views and how they lose all their comments and whitespace formatting. The article also describes a workaround for it. (Interestingly, a few of the bugs I ran into at the time are now resolved, which means you can do without the kludges I had to turn to in order to get it to work.)

Apart from views, there really is no limitation in the capabilities of the MySQL Server to keep the comments inside code associated with stored routines. (With stored routines, I mean: stored procedures, functions, triggers and events).

There's a really plain and simple explanation for the endurance of the "comment-stripping-myth". The myth remains alive because a lot (if not most) people exclusively use the mysql command line client command line tool for their MySQL work. The mysql interprets the user input, and strips the comments before it sends the command to the server. So any comments never even arrive at the server side.

MySQL Command Line Client Interpreter


The mysql command line client tool implements a simple state-machine to discover when the user input should be sent to the server. For example, the ; you normally type after a statement, and the delimiter command you use in order to create a stored procedure using the command line client: those are all elements that don't really belong to the SQL language itself. They exist solely for the benefit of the mysql command line client tool, because it has to have some way of knowing when the user is done typing whatever is to be sent to the server.

The mysql command line interpreter does not limit itself to interpreting the statement delimiter. It also recognizes that the user input initiates a comment. Let's take a closer look at that behaviour:

mysql>

This is what the mysql prompt normally looks like. When it looks like this, it is ready to accept input. Let's type some input, and then skip to the new line:

mysql> select version()
->

As you can see, the client tool notices that we started to type some input. The prompt changes from mysql> to ->, notifying the user that all the subsequent input will be seen as belonging to the current statement. When we now type the default statement delimiter ;, the command line tool will send the gathered input to the server, wait to receive the result and output that to the user:

mysql> select version()
-> ;
+-----------------+
| version() |
+-----------------+
| 5.1.15-beta-log |
+-----------------+
1 row in set (0.00 sec)

mysql>

It also displays a new fresh prompt, ready to receive new input.

So what happens when we initiate a comment? Well, the mysql command line client notices that too. Single line comments are initiated by a # character or the -- (dash-dash-space) sequence, which will effectively stop interpreting the subsequent input until a newline character is seen. This can be demonstrated by typing the statement delimiter right after the comment is initiated, and then skipping to the next line:

mysql> select version() # single line comment;
-> -- dash dash space;
-> ;

As you can see, the ; does not delimit the statement if it appears on the same line after the # or -- single line comment initiators. The statement delimiter is not the only character that ceases to be given a special meaning: all input appearing on the line after the comment was initiated will not be interpreted.

Comments can also span multiple lines using the C-style comment syntax:

mysql> select /*
/*> comments;
/*> */ version();

So, the /* sequence initiates a comment, and all subsequent input, including new lines, will not be interpreted as having special meaning. That is, until the next */ sequence is encountered, delimiting the comment. You can see that the command client tool knows we're inside the multi-line comment, because the prompt changes accordingly: the second and third line have the /*> prompt rather than the usual -> prompt.

Comments are stripped before sending the command to the server


We've just seen that the mysql command line client interprets the user input until it sees a statement delimiter. When it sees one, it treats the gathered input as one command, and sends it to the server. So far, nothing strange is happening. To observe the effect of comments stripping, we need to create a little stored procedure.

Stored procedures, functions, triggers and events are also created with a single statement. However, these types of statements differ from the single SELECT statements we just observed, because these statements can themselves contain a statement sequence. So, if the mysql command line client is to be used to create them, there must be a mechanism to distinguish the delimiter that is used to separate the statements inside the procedure from the delimiter that is used to mark the end of the outer statement so it can be sent to the server.

This is achieved using a special mysql command line client command, the delimiter command. The delimiter command is used to define a word or a delimiting sequence as the statement delimiter. Let's use the delimiter command:

mysql> delimiter $$

This command tells the mysql command line client command to use the $$ sequence as a marker to gather the user input and send it to the server. It's not sent to the server - it only serves to modify the behaviour of the local command interpreter. The default statement delimiter ; may still appear at the end of a statement, but it will simply be ignored:

mysql> select version()
-> ;
-> $$
+-----------------+
| version() |
+-----------------+
| 5.1.15-beta-log |
+-----------------+
1 row in set (0.00 sec)

mysql>

Now, we can create a stored routine that contains a statement sequence:

mysql> CREATE PROCEDURE p()
-> BEGIN
-> /******************
/*> * A simple procedure
/*> *******************/
-> SELECT version();
-> SELECT schema();
-> END;
-> $$
Query OK, 0 rows affected (0.00 sec)

The actual statement sequence is contained in the BEGIN..END statement. The BEGIN..END statement is therefore also a compound statement.

The statement list itself is made up of only two SELECT statements. Each of these needs to be properly delimited using the ; statement delimiter. Here, the ; symbol is fixed, and cannot be changed. In this context, the ; is interpreted by the MySQL Server. Inside the BEGIN..END block is also a multi line comment. Technically, comments are not statements, however we did put it inside the block with the intention of keeping it there.

In order to be able to send the CREATE PROCEDURE statement as a whole to the server, the mysql command line client needs to stop interpreting the ; symbol as a statement delimiter. That's why we had to redefine the mysql command line client statement delimiter to something other than ;. In this example, the $$ was chosen. As soon as the mysql command line client sees the $$ sequence, it will gather the user input, and send it to the server as one complete statement, which tells the server to create the procedure.

To change the mysql command line client statement delimiter back to the original, simply use the DELIMITER command again:

mysql> delimiter ;

We can use the SHOW CREATE PROCEDURE statement or the ROUTINES view in the information_schema to see what the procedure looks like according to the server:

mysql> SELECT routine_definition
-> FROM information_schema.routines
-> WHERE routine_schema = schema()
-> AND routine_name = 'p'
-> ;
+-------------------------------------------------------+
| routine_definition |
+-------------------------------------------------------+
| BEGIN

SELECT version();
SELECT schema();
END |
+-------------------------------------------------------+
1 row in set (0.01 sec)

mysql>

This result clearly shows that the comment is now gone, although we took no explicit action to remove the comment. So where did it go? How do we know for sure it is due to the mysql client tool that the comment is gone?

The simplest way to prove it, is to use the either the MySQL Query Browser or the MySQL Administrator GUI Tools. For example, using the MySQL Administrator, you can find the procedures by clicking the "Catalogs" item in the top sidebar, and then selecting the database that contains the procedure in the bottom sidebar. The procedures are all available in the "Stored Procedures" tab:
MySQLAdministratorStoredProcedures
There, you can select the procedure. Hitting the "Edit" putting pops up a small editor:
MySQLAdministratorEditStoredRoutine
By pasting the original code there, and then pushing the "Execute" button, we recreate the procedure. If we now use the mysql command line client again to see what the procedure looks like, we can see that the comment is exactly were we put it originally:

mysql> SELECT routine_definition
-> FROM information_schema.routines
-> WHERE routine_schema = schema()
-> AND routine_name = 'p'
-> ;
+---------------------+
| routine_definition |
+---------------------+
| BEGIN
/******************
* A simple procedure
*******************/
SELECT version();
SELECT schema();
END |
+---------------------+
1 row in set (0.00 sec)

You might argue that this solves the problem: if we simply switch and use these GUI tools, we will never have this problem again. Well, that's true, and maybe that works for you. However, in a lot of cases, I like the mysql command line tool better for a number of reasons (not discussed here).

Why I like code comments stored in the database


I like to write comments inside stored procedures. I use them to document the algorithm as well as it's usage. I picked up the habit when I worked as application developer writing tons of Oracle PL/SQL. Parts of the PL/SQL code of Oracle's own builtin packages (package headers, sometimes package bodies) can be queried using the data dictionary, and usually the comments inside the PL/SQL source code are enough to figure out how to use the builtin package.

Now I know a lot of people will say: "Nah, the database was not meant for all that, you should write proper documentation!" or "Why don't you use a version control system for that." Of course, I agree that proper documentation should be written, and I agree that version control systems should be used. However, the comments inside the stored procedure code serve another purpose, and I think it's a pity it's discarded by the mysql command line client tool.

  • Documentation is usually not written at the level of the implementation. Hacks, work-arounds and tricks used to implement a certain algorithm usually don't make it to the documentation. However, it is important to record their rationale, and in my opinion, there is no better place than the code itself.
  • A version control system should be in place in the development environment. However, when an application is distributed, the actual development code (including the comments) might not be readily available when it's most needed. Keeping the code and comments together inside the database server can be a life-saver if somebody suddenly runs into problems, because it enhances the possibilities to modify the code.
  • What about Licensing? Suppose I want to include, say a GPL license note with the code? I'd rather keep it as close to my stored procedure as possible.

Ok, have it your way, and try without comments


To illustrate the value of retaining the comments, consider the following function:

CREATE FUNCTION f_check_ccno(
p_ccno BIGINT
)
RETURNS TINYINT
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY INVOKER
BEGIN
DECLARE v_ccno VARCHAR(20)
DEFAULT reverse(
cast(p_ccno AS CHAR(20))
);
DECLARE v_len_ccno TINYINT
DEFAULT length(v_ccno);
DECLARE v_i TINYINT
DEFAULT 1;
DECLARE v_num TINYINT
DEFAULT 1;
DECLARE v_check BIGINT
DEFAULT 0;
WHILE v_i <= v_len_ccno DO
SET v_num := cast(
substr(v_ccno,v_i,1)
AS SIGNED
);
IF v_i%2=0 THEN
SET v_num := 2*v_num;
IF v_num > 9 THEN
SET v_num := v_num - 9;
END IF;
END IF;
SET v_check := v_check + v_num;
SET v_i := v_i + 1;
END WHILE;
RETURN (v_check%10)=0;
END;

So, what kind of function is this? Well, the function is called f_check_ccno, and it accepts a p_ccno parameter that is of the BIGINT type. Supposedly, ccno means something, assuming that f_ and p_ are prefixes for "function" and "parameter" respectively. The function returns a TINYINT which we assume encodes the validation result in some way.

It becomes more difficult if we want to find out what kind of validation we're dealing with. I invite you to try it: just peel it off, statement by statement and expression by expression Just guess what this thing is supposed to do.

Most people with some programming experience will have only little difficulties if at all to find out what the function does. That's hardly a suprise: the MySQL Stored Procedure language is a structured higher programming language, and it consists mostly of English keywords. However, the real problem with software program source code is to find out what it means or what was intended. Programming languages have no construct to code for purpose. All speculation on the meaning of this function so far has been derived from the identifiers. We just assume that the names have something to do with the role they play in the code.

(People: try and guess this. Post a comment here and describe what you think this function does. It is actually a useful function - I didn't just make it up. And, oh yeah...I put in a little snag - a bug. Can you find it? In one or two weeks, I'll post it as a snippet at MySQL Forge, with comments and a proper description.)

Now why do we even care? Suppose we inherit someones database and we happen to bump into this function. Of course, we assume the function is there for a reason, and it's might be used by another piece of code, say, a view, or a trigger, or maybe even a query issued by the application. But can't we just treat this function as a black box, and trust it to do whatever it's supposed to do?

Well, sure we can! And if everything is working fine, there's no reason to change anything. Of course we're just ignoring that some day, things might not work fine anymore. Bugs may suddenly occur, or we may find that the function does not scale well. Another likely scenario is a change of business, requiring that we re-evaluate or change the business rules and the software that enforces it. So one way or the other, we're going to have to deal with this function. In the worst case, we mightfind ourselves rebuilding part of the system, gathering and analyzing business requirements again. Once we're done, we might find that we've ended up with pretty much the same function we started with, the only difference being that we re-invented it ourselves.

Of course, you might get pretty fed up with the job by the time that's finished. You'll find a new opportunity and find a new occupation, leaving your successor with a new function called f_check_cc_number() that accepts a BIGINT UNSIGNED parameter called p_cc_number, and that returns a ENUM('PASS','FAIL') value. Needless to say, this is a vast improvement over the original, and has a really nifty and superior implementation. Of course, it still won't have comments because the mysql command line client has stripped them out again...

Yup. I guess you know where this is heading.

Great. Now what do we do?


Well, there are several options:

  • Switch to the GUI tools

  • Modify the mysql command line client

  • Use a little trick...


I already discussed the first option more or less. If it works for you, great. I like the GUI Tools too, but for a lot of work I just feel more comfortable with the mysql command line client.

The second option is something I'm seriously considering. It would be great to be able to turn the comment stripping on using a configuration option. The default behaviour would not be changed, and people that want to turn comment stripping off have a clean way of doing so. I explored the source code a little bit, and although I'm not a C/C++ programmer, I should be able to pull this off.

Of course, everybody can do this, and contribute the code. If you want to get started, get the source code, and hack away. To get you started, there is a very simple hack that will make mysql preserve at least the single line comments.

To do so, open the {mysql-bk}/client/mysql.cc source file and goto line 1108. There you will find something like this:

if (!in_string && (line[0] == '#' ||
(line[0] == '-' && line[1] == '-') ||
line[0] == 0))
continue; // Skip comment lines

Removing these lines will make mysql preserve all single line comments that start a line. At line 1330 a similar snippet strips the single line comments that are start within a line:

else if (!*ml_comment && (!*in_string && (inchar == '#' ||
inchar == '-' && pos[1] == '-' &&
my_isspace(charset_info,pos[2]))))
break; // comment to end of line

I tried it, and this seems to work just fine. Of course, making the patch really worth while will, put mildly, probably take a bit more overview of the source than I have now. For example, what keeps nagging me is the question "Why on earth are the comments stripped in the first place?" Anyway.

Finally: A little trick....


There is a dirty little trick that might just work fine for the time being. If you read the manual section that describes the comment syntax you might notice that there are two different forms of the multi-line comment. We already discussed the plain "C-style" comments. There is another syntax that is meant as a compatibility syntax rather than a comment syntax. It goes like this:

/*! MySQL-specific code */

The purpose of these "comments" is to be able to write portable queries. The "comment" is not really a comment; rather, it's a real command that might not be understood by all servers. The manual uses this example:

SELECT /*! STRAIGHT_JOIN */ col1 FROM table1,table2 WHERE ...

So, a MySQL server would understand the STRAIGHT JOIN modifier, and execute the statement accordingly, whereas other servers won't recognize this as anything else but a comment, and ignore it entirely. A special form of this syntax is frequently used in the output of mysqldump to create database dumps that are portable between MySQL versions. Here's an example from the script for the world database:

/*!40101 SET NAMES latin1 */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;

So, you see, a n.nn.nn scheme is used to target only a specific version of the MySQL Server. I tried a little bit, and I found out that this does the trick pretty well:

/*!999999
*
*/

So, note that I'm using six digits rather than five. Of course, it's ugly, but it works. And yes, I might have a problem as soon as MySQL 99.99.99 hits the shelves, but maybe that is just enough time for me to learn enough C/C++ to come up with a satisfactory patch for the mysql command line client. :)

DuckDb Performance: min, max, and median vs quantile

I was playing with some classical statistics in DuckDB and I ran into something I'd like to share. It's about the measures minimum,...