Thursday, July 14, 2011

The Thursday Quote - Linus Torvalds


"95 percent of all programmers think they are in the top 5 percent, and the rest are certain they are above average"
- Linus Torvalds, quoted in The One-second War (What Time Will You Die?) by Poul-Henning Kamp, acmqueue, April 6, 2011

Here is the context...

"But Linus' [Torvalds] observation that "95 percent of all programmers think they are in the top 5 percent, and the rest are certain they are above average" should not be taken lightly: very few programmers have any idea what the difference is between "wall-clock time" and "interval time," and leap seconds are way past rocket science for them. (For example, Posix defines only a pthread_cond_timedwait(), which takes wall-clock time but not an interval-time version of the call.)"
If Mr. Torvalds is correct, he is only approximately so: I regard myself a below-average programmer and therefore I fall outside "the rest". If I was above average I would not have to work so hard to produce good code.

And I'm not even confident about the "good code" part... trying hard does not always lead to success.

Even worse, I know that many folks despise my coding style: too verbose, too vertical, too much white space, too many long names, too many underscores, not enough uPpEr and LoWeRcAsE, too many explicit name.qualifications, too many comments, and especially [blasphemy] not enough abstraction, polymorphism or inheritance [/blasphemy].

None of that bothers me, however. Only one complaint gets my attention: "I do not understand what you have written."

Oh, and of course, "Your code does not work." ...it goes without saying that gets my attention! :)


Wednesday, July 13, 2011

Using SQL to control a batch file

Whenever a developer starts the Foxhound build process, this exhortation appears:



Here's what it's saying: Go to this page in Foxhound and check to see if there are any run-time error messages that need to be dealt with:



Here's the code that produces the exhortation:


ECHO OFF
ECHO *************************************************************************
ECHO *****
ECHO ***** Have you looked at Foxhound Options - Display Exceptions?
ECHO *****
ECHO ***** Before you rebuild your Foxhound database and wipe out what was in
ECHO ***** the old copy, you might want to see if there were any errors you
ECHO ***** need to look at... or at least copy-and-paste into your to-do list.
ECHO *****
ECHO *************************************************************************
ECHO ON
PAUSE

Wouldn't it be easier for the developer if the build process checked to see if there actually ARE any exceptions?

Here's a Windows batch file that demonstrates how to use dbisql.com in batch mode, together with the ISQL EXIT statement, to do just that:

ECHO OFF

"%SQLANY12%\bin64\dbisql.com"^
-c "ENG=foxhound1;DBN=f;UID=ADHOC;PWD=SQL"^
-q^
CREATE VARIABLE rc INT; SET rc = 0; IF EXISTS ( SELECT * FROM rroad_exception ) THEN SET rc = 999 ENDIF; EXIT rc;

ECHO ERRORLEVEL = %ERRORLEVEL%

IF %ERRORLEVEL% == 999 ( ECHO rroad_exception is known to have rows ) ELSE ( ECHO rroad_exception is not known to have any rows )

PAUSE

The dbisql.com command on lines 3 through 6 does the following:
  • it connects to the Foxhound database if it's running, using the ADHOC user id provided by Foxhound for (you guessed it) ad-hoc reporting,

  • it specifies -q to suppress all display output except for error messages, and

  • it runs the SQL statements on line 6 instead of executing a script via the ISQL READ statement. Another alternative to coding out all the statements is to CALL a stored procedure, but this dbisql.com command is standalone; i.e., it doesn't require a separate SQL script or stored procedure to be created ahead of time.
The SQL statements on line 6 are all squished onto a single line because dbisql.com can't cope with multiple lines of SQL on the command line... or at least, I haven't figured out how to code line breaks in the SQL statement section.

All the carp code on line 6 to CREATE and use a connection-level variable is necessary because EXIT is an ISQL statement, not a SQL statement, and it can't be coded inside an IF THEN ELSE control block. The IF THEN block is sent to the SQL Anywhere engine to be processed, and the engine would choke throw an error if it saw an EXIT statement.

The IF %ERRORLEVEL% == 999 command on line 10 displays "rroad_exception is known to have rows" if the EXIT rc returned 999. There are two other likely %ERRORLEVEL% values: 0 for "it's all good, there's nothing to look at" and 9 for "can't connect, and therefore the number of rows is unknown"... that's why the ELSE ECHO message is so vague.
Note: The Windows documentation of the IF command talks a bit about %ERRORLEVEL% versus ERRORLEVEL, but it doesn't actually show an example of how IF %ERRORLEVEL% is so much easier think about, code and read than the old-fashioned IF ERRORLEVEL... folks who code batch files for a living know what I'm talkin' about.
Here's what the batch file displays in all three cases, rc = 999, 0 and 9:







Here's the snippet of code that was added to the Foxhound build process:

"%SQLANY12%\bin64\dbisql.com"^
-c "ENG=foxhound1;DBN=f;UID=ADHOC;PWD=SQL"^
-q^
CREATE VARIABLE rc INT; SET rc = 0; IF EXISTS ( SELECT * FROM rroad_exception ) THEN SET rc = 999 ENDIF; EXIT rc;

IF %ERRORLEVEL% == 999 ( GOTO rroad_exception_is_known_to_have_rows ) ELSE ( GOTO rroad_exception_is_not_known_to_have_any_rows )

:rroad_exception_is_known_to_have_rows
ECHO OFF
ECHO *************************************************************************
ECHO *****
ECHO ***** The rroad_exception table is NOT EMPTY !!!
ECHO *****
ECHO ***** Before you rebuild your Foxhound database and wipe out what was in
ECHO ***** the old copy, you might want to see if there were any errors you
ECHO ***** need to look at... or at least copy-and-paste into your to-do list.
ECHO *****
ECHO *************************************************************************
ECHO ON
PAUSE

:rroad_exception_is_not_known_to_have_any_rows

Here's the new improved exhortation:



BTW, the new exhortation was added to the build process, it didn't replace the old one... you can never have too many exhortations when you're looking for bugs!


Monday, July 11, 2011

Transactional Upload With MobiLink


True or False?

It is a fundamental rule of The MobiLink Synchronization Doctrine that uploaded changes are processed as a single all-or-nothing transaction on the consolidated database.
Here's a clue:
"The MobiLink server incorporates changes uploaded from each MobiLink client into the consolidated database in one transaction. It commits these changes once it has completed inserting new rows, deleting old rows, making updates, and resolving any conflicts."
- Transactions in the synchronization process
OK, true or false?




No... the correct answer is "False"


It was "True" for many years, but... did you know that since version 9.0.1 of SQL Anywhere it has been possible to break the one-transaction-rule for the upload stream?
"When you use -tu, you create a transactional upload: dbmlsync uploads each transaction on the remote database as a distinct transaction. The MobiLink server applies and commits each transaction separately when it is received."
- dbmlsync -tu option

It *is* well documented, but not easy to find, perhaps testimony to the stealth nature of SQL Anywhere.

If you're familiar with replication via SQL Remote or Replication Server, transactional upload is similar to how uploads work with dbremote.exe (the SQL Remote client) and dbltm.exe (the Log Transfer Manager, also known as... deep breath... the SQL Anywhere Replication Agent for Sybase Replication Server). Some folks have designed their systems to expect uploads to be applied to the consolidated database in the same order as the original transactions were applied to the remote databases. With the end of support for SQL Remote for ASE and for the Log Transfer Manager altogether, MobiLink is now pretty much the only game in town if you're not using SQL Anywhere for the consolidated database.

But wait, there's more...
  • MobiLink transactional upload may help with performance if the consolidated database is happier with small transactions than a giant all-or-nothing upload transaction.

  • Transactional upload may also help with robustness by allowing part of an upload to succeed before a failure occurs on an unreliable communication line between the MobiLink client and server.

  • The mlsrv12 -tx n option tells the MobiLink server to batch groups of n transactions and commit them together, when you use the dbmlsync -tu option on the MobiLink client side to get transactional upload.

  • Also note: The dbmlsync inc (increment) extended option is an alternative to dbmlsync -tu. When you specify inc=1M, for example, the single all-or-nothing upload transaction is broken into 1M (very approximate) chunks and each chunk is committed on the consolidated database. This isn't the same as transactional upload, not even close, but like transactional upload it might help with performance and robustness.

The Dark Side of Transactional Upload


MobiLink transactional upload ain't exactly the same as how SQL Remote and Replication Server work: transaction order isn't exactly the same as operation order. MobiLink still uploads full row images rather than column-level changes as they were recorded in the transaction log, and multiple operations on the same row in the same transaction are effectively "coalesced" (combined) into the final row image that MobiLink uploads.

Who cares? Well, in most cases, you don't. Most folks don't need transactional upload to make their systems work properly; the only reason they might use it is for performance and/or robustness.

Of the remainder (folks who need transactional upload), most don't care about transaction-versus-operation-order; as long as the end result of each transaction is the same on the remote and consolidated databases, they're happy.

Who's left? Folks who need transactional upload, but can't use it without changing the way their system works because they really depend on the order of operations within a transaction.

The following end-to-end demonstration uses SQL Anywhere 11 for the remote database and Microsoft SQL Server 2008 for the consolidated database
  • to show how MobiLink transactional upload works, and

  • to demonstrate how the difference between transaction order and operation order might affect the consolidated database.


The phrase "end-to-end demonstration" in the previous section means just that. The code makes no assumptions except
  • that you have a Microsoft SQL Server 2008 server up and running, and

  • that you have a working copy of SQL Anywhere 11 installed.
The code does not assume you have any test databases; it starts from scratch with CREATE DATABASE and dbinit.exe.

Having said that, not every line of code is explained here, just the pertinent parts. Feel free to skip over the parts you don't care about, and if you don't like the verbosity please pay close attention to what Alice has to say:

Dilbert.com


Step 1: Create and set up the consolidated database.

1_create_and_set_up_cons.bat

"c:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe"^
-d master^
-i "s1_create_and_set_up_cons.sql"^
-I^
-P j68Fje9#fyu489^
-S localhost^
-U sa

PAUSE

s1_create_and_set_up_cons.sql

---------------------------------------------------------------------
-- Create separate database "mltest" (change the paths as desired).

USE master
GO

CREATE DATABASE mltest
ON
( NAME = mltest,
FILENAME = 'D:\data\mltest\mltest.mdf',
SIZE = 100MB )
LOG ON
( NAME = mltest_log,
FILENAME = 'D:\data\mltest\mltest.ldf',
SIZE = 100MB )
GO

---------------------------------------------------------------------
USE mltest
GO

SET QUOTED_IDENTIFIER ON
GO

---------------------------------------------------------------------
CREATE TABLE account_balance (
account_number VARCHAR ( 10 ) NOT NULL,
amount DECIMAL ( 15, 2 ) NOT NULL DEFAULT 0,
limit_exceeded VARCHAR ( 1 ) NOT NULL DEFAULT 'N' CHECK ( limit_exceeded IN ( 'Y', 'N' ) ),
PRIMARY KEY ( account_number ) )
GO

---------------------------------------------------------------------
BEGIN TRANSACTION
INSERT account_balance ( account_number ) VALUES ( '100' )
INSERT account_balance ( account_number ) VALUES ( '200' )
INSERT account_balance ( account_number ) VALUES ( '300' )
INSERT account_balance ( account_number ) VALUES ( '400' )
INSERT account_balance ( account_number ) VALUES ( '500' )
INSERT account_balance ( account_number ) VALUES ( '600' )
INSERT account_balance ( account_number ) VALUES ( '700' )
INSERT account_balance ( account_number ) VALUES ( '800' )
INSERT account_balance ( account_number ) VALUES ( '900' )
COMMIT TRANSACTION
GO

---------------------------------------------------------------------
CREATE TRIGGER tru_account_balance ON account_balance AFTER INSERT, UPDATE
AS
BEGIN

-- Flag the row as soon as it exceeds the limit.

UPDATE account_balance
SET account_balance.limit_exceeded = 'Y'
FROM inserted
WHERE account_balance.account_number = inserted.account_number
AND account_balance.limit_exceeded = 'N'
AND account_balance.amount > 5000.00

END
GO

The CREATE TABLE supports one application requirement: Store the current balance for each account.

The INSERT statements set up accounts 100 through 900 to be used in examples that will be numbered 1 through 9.

The CREATE TRIGGER supports another application requirement: Flag each row as soon as the balance exceeds $5000, and preserve that flag setting even if the balance drops to $5000 or lower at a later point.


Step 2: Exercise the trigger.

2_update_cons_to_demonstrate_trigger.bat

"c:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe"^
-d mltest^
-i "s2_update_cons_to_demonstrate_trigger.sql"^
-I^
-P j68Fje9#fyu489^
-S localhost^
-U sa

PAUSE

s2_update_cons_to_demonstrate_trigger.sql

-- Example 1: One local transaction, one operation.

BEGIN TRANSACTION
UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '100'
COMMIT TRANSACTION
GO

-- Example 2: One local transaction, two operations.

BEGIN TRANSACTION
UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '200'
UPDATE account_balance SET amount = amount - 1000.00 WHERE account_number = '200'
COMMIT TRANSACTION
GO

-- Example 3: Two local transactions, one operation each.

BEGIN TRANSACTION
UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '300'
COMMIT TRANSACTION
GO

BEGIN TRANSACTION
UPDATE account_balance SET amount = amount - 1000.00 WHERE account_number = '300'
COMMIT TRANSACTION
GO

The script above contains three examples:
  • Example 1 performs one single operation in a single transaction to cause the account balance to exceed $5000 and remain there.

  • Example 2 performs two operations in a single transaction to cause the account balance to exceed $5000 and then fall back down to $5000.

  • Example 3 does the same thing, but it uses a separate transaction for each operation.


Step 3: Show what the trigger did.

3_display_cons.bat

"c:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe"^
-d mltest^
-e^
-q "SELECT * FROM account_balance ORDER BY account_number"^
-I^
-P j68Fje9#fyu489^
-S localhost^
-U sa

In all three examples the flag should be set to 'Y' and left there... and the consolidated database confirms that:

SELECT * FROM account_balance ORDER BY account_number
account_number amount limit_exceeded
-------------- ----------------- --------------
100 6000.00 Y
200 5000.00 Y
300 5000.00 Y
400 .00 N
500 .00 N
600 .00 N
700 .00 N
800 .00 N
900 .00 N

(9 rows affected)
1>


Step 4: Add MobiLink support for SQL Server to the consolidated database.

4_run_syncmss_on_cons.bat

"c:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe"^
-d mltest^
-i "%SQLANY11%\MobiLink\setup\syncmss.sql"^
-I^
-P j68Fje9#fyu489^
-S localhost^
-U sa

PAUSE


Step 5: Create the MobiLink event scripts on the consolidated database.

5_set_up_MobiLink_scripts_on_cons.bat

"c:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe"^
-d mltest^
-i "s5_set_up_MobiLink_scripts_on_cons.sql"^
-I^
-P j68Fje9#fyu489^
-S localhost^
-U sa

PAUSE

s5_set_up_MobiLink_scripts_on_cons.sql

USE mltest
GO

SET QUOTED_IDENTIFIER ON
GO

---------------------------------------------------------------------
EXECUTE ml_add_column 'v1', 'account_balance', NULL, NULL
GO

EXECUTE ml_add_column 'v1', 'account_balance', 'account_number', NULL
GO

EXECUTE ml_add_column 'v1', 'account_balance', 'amount', NULL
GO

---------------------------------------------------------------------
EXECUTE ml_add_table_script 'v1', 'account_balance', 'upload_insert', NULL
GO

EXECUTE ml_add_table_script 'v1', 'account_balance', 'upload_insert', '
INSERT account_balance
( account_number,
amount )
VALUES ( {ml r.account_number},
{ml r.amount} )'
GO

---------------------------------------------------------------------
EXECUTE ml_add_table_script 'v1', 'account_balance', 'upload_update', NULL
GO

EXECUTE ml_add_table_script 'v1', 'account_balance', 'upload_update', '
UPDATE account_balance
SET amount = {ml r.amount}
WHERE account_number = {ml r.account_number}'
GO

---------------------------------------------------------------------
EXECUTE ml_add_table_script 'v1', 'account_balance', 'upload_delete', NULL
GO

EXECUTE ml_add_table_script 'v1', 'account_balance', 'upload_delete', '
DELETE account_balance
WHERE account_number = {ml r.account_number}'
GO

The SQL Server EXECUTE statements set up ordinary MobiLink event scripts for handling INSERT, UPDATE and DELETE operations that have been uploaded from the remote to the consolidated database.


Step 6: Set up the remote database.

6_create_and_set_up_remo.bat

"%SQLANY11%\bin32\dbinit.exe"^
remo.db

"%SQLANY11%\bin32\\dbspawn.exe"^
-f "%SQLANY11%\bin32\dbeng11.exe"^
-o dbeng11_log_remo.txt^
-x tcpip^
remo.db

"%SQLANY11%\bin32\dbisql.com"^
-c "ENG=remo;DBN=remo;UID=dba;PWD=sql"^
READ ENCODING Cp1252 "s6_create_and_set_up_remo.sql"

PAUSE

s6_create_and_set_up_remo.sql

CREATE TABLE account_balance (
account_number VARCHAR ( 10 ) NOT NULL,
amount DECIMAL ( 15, 2 ) NOT NULL DEFAULT 0,
PRIMARY KEY ( account_number ) )
GO

---------------------------------------------------------------------
INSERT account_balance ( account_number ) VALUES ( '400' );
INSERT account_balance ( account_number ) VALUES ( '500' );
INSERT account_balance ( account_number ) VALUES ( '600' );
INSERT account_balance ( account_number ) VALUES ( '700' );
INSERT account_balance ( account_number ) VALUES ( '800' );
INSERT account_balance ( account_number ) VALUES ( '900' );
COMMIT;

---------------------------------------------------------------------
CREATE PUBLICATION p1 (
TABLE account_balance ( account_number,
amount ) );

CREATE SYNCHRONIZATION USER "1" TYPE tcpip;

CREATE SYNCHRONIZATION SUBSCRIPTION TO p1 FOR "1";

The script above creates the account_balance table in the remote database and inserts 6 rows to exactly match the corresponding 6 rows in the consolidated database, except for the fact that the limit_exceeded column does not exist on the remote database.

The remaining CREATE statements serve to define how the remote database will be synchronized with the consolidated database, as well as defining the point in time after which changes will be processed by the first synchronization. In other words, the earlier INSERT operations won't be uploaded, but any subsequent changes will be.


Step 7: Update the remote database to demonstrate MobiLink "Classic" mode.

7_update_remo_to_demonstrate_dbmlsync_without_tu.bat

"%SQLANY11%\bin32\dbisql.com"^
-c "ENG=remo;DBN=remo;UID=dba;PWD=sql"^
READ ENCODING Cp1252 "s7_update_remo_to_demonstrate_dbmlsync_without_tu.sql"

PAUSE

s7_update_remo_to_demonstrate_dbmlsync_without_tu.sql

-- Example 4: One synchronization without -tu, one transaction, one operation.

UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '400';
COMMIT;

-- Example 5: One synchronization without -tu, one transaction, two operations.

UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '500';
UPDATE account_balance SET amount = amount - 1000.00 WHERE account_number = '500';
COMMIT;

-- Example 6: One synchronization without -tu, two transactions, one operation each.

UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '600';
COMMIT;

UPDATE account_balance SET amount = amount - 1000.00 WHERE account_number = '600';
COMMIT;

SELECT * FROM account_balance ORDER BY account_number;

The script above repeats the three examples from earlier, except this time the updates are performed on the remote database:
  • Example 4 performs one single operation in a single transaction to cause the account balance to exceed $5000 and remain there.

  • Example 5 performs two operations in a single transaction to cause the account balance to exceed $5000 and then fall back down to $5000.

  • Example 6 does the same thing, but it uses a separate transaction for each operation.
Here's what the remote database looks like at this point:

account_number amount
--------------------------------
400 6000.00
500 5000.00
600 5000.00
700 0.00
800 0.00
900 0.00

(6 rows)


Step 8: Start the MobiLink server on the consolidated database.

8_run_mlsrv11.bat

"%SQLANY11%\bin32\mlsrv11.exe"^
-c "DSN=mltest;UID=sa;PWD=j68Fje9#fyu489"^
-fr^
-o mlsrv11_log_cons.txt^
-os 10M^
-ppv 60^
-vcefhimnopqrstu^
-zu+




Step 9: Run a synchronization session in MobiLink "Classic" mode.

9_run_dbmlsync_without_tu.bat

"%SQLANY11%\bin32\dbmlsync.exe"^
-c "ENG=remo;DBN=remo;UID=dba;PWD=sql"^
-e "adr='host=localhost';sv=v1"^
-o dbmlsync_log_remo_from_step_9.txt^
-os 10M^
-uo^
-vnorsu^
-x



You can run 3_display_cons.bat again to see what happened during the MobiLink "Classic" mode upload above, and the news is not good: Only one of the three uploaded rows 400, 500 and 600 has limit_exceeded set to 'Y' even though all three rows exceeded $5000 at one point or another before they were uploaded.

SELECT * FROM account_balance ORDER BY account_number
account_number amount limit_exceeded
-------------- ----------------- --------------
100 6000.00 Y
200 5000.00 Y
300 5000.00 Y
400 6000.00 Y
500 5000.00 N
600 5000.00 N
700 .00 N
800 .00 N
900 .00 N

(9 rows affected)
1>

That's what happens when you're not using transactional upload: MobiLink only sends the final version of each updated row, and so when the trigger fired on the consolidated database it did not set the flag for the two rows where the final amount was $5000.


Step 10: Update the remote database to demonstrate transactional upload.

10_update_remo_to_demonstrate_dbmlsync_tu.bat

"%SQLANY11%\bin32\dbisql.com"^
-c "ENG=remo;DBN=remo;UID=dba;PWD=sql"^
READ ENCODING Cp1252 "s10_update_remo_to_demonstrate_dbmlsync_tu.sql"

PAUSE

s10_update_remo_to_demonstrate_dbmlsync_tu.sql

-- Example 7: One synchronization with -tu, one transaction, one operation.

UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '700';
COMMIT;

-- Example 8: One synchronization with -tu, one transaction, two operations.

UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '800';
UPDATE account_balance SET amount = amount - 1000.00 WHERE account_number = '800';
COMMIT;

-- Example 9: One synchronization with -tu, two transactions, one operation each.

UPDATE account_balance SET amount = amount + 6000.00 WHERE account_number = '900';
COMMIT;

UPDATE account_balance SET amount = amount - 1000.00 WHERE account_number = '900';
COMMIT;

SELECT * FROM account_balance ORDER BY account_number;

The script above repeats the same three examples yet again:
  • Example 7 performs one single operation in a single transaction to cause the account balance to exceed $5000 and remain there.

  • Example 8 performs two operations in a single transaction to cause the account balance to exceed $5000 and then fall back down to $5000.

  • Example 9 does the same thing, but it uses a separate transaction for each operation.
Here's what the remote database looks like at this point:

account_number amount
--------------------------------
400 6000.00
500 5000.00
600 5000.00
700 6000.00
800 5000.00
900 5000.00

(6 rows)


Step 11: Run a synchronization session using transactional upload.

11_run_dbmlsync_with_tu.bat

"%SQLANY11%\bin32\dbmlsync.exe"^
-c "ENG=remo;DBN=remo;UID=dba;PWD=sql"^
-e "adr='host=localhost';sv=v1"^
-o dbmlsync_log_remo_from_step_11.txt^
-os 10M^
-tu^
-uo^
-vnorsu^
-x



The news on the consolidated database is a bit better when transactional upload is used: Two of the three uploaded rows 700, 800 and 900 have limit_exceeded set to 'Y'. However, two updates on row 800 were performed in one single transaction on the remote database, and MobiLink combines all the changes made to one row in one transaction even though transactional upload is used, so... when the trigger fires for that row, it only fires once, and it sees the final value $5000, so limit_exceeded remains 'N'.

SELECT * FROM account_balance ORDER BY account_number
account_number amount limit_exceeded
-------------- ----------------- --------------
100 6000.00 Y
200 5000.00 Y
300 5000.00 Y
400 6000.00 Y
500 5000.00 N
600 5000.00 N
700 6000.00 Y
800 5000.00 N
900 5000.00 Y

(9 rows affected)
1>

The situation is better for row 900 (limit_exceeded is set to 'Y') because the two updates were done in separate transactions, and that's how you have to change your application if you don't want operations combined when they are uploaded: you have to put different operations on the same row in different transactions.

The next two steps perform cleanup when you're done, or when you want to start the demonstration over from scratch.

Step 12: Cleanup - Drop the consolidated database

12_drop_cons.bat

PAUSE Are you sure you want to drop the consolidated database?

"c:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe"^
-d master^
-i "s12_drop_cons.sql"^
-I^
-P j68Fje9#fyu489^
-S localhost^
-U sa

PAUSE

s12_drop_cons.sql

USE master
GO

BEGIN TRY
DROP DATABASE mltest
END TRY
BEGIN CATCH
-- ignore error
END CATCH
GO


Step 13: Cleanup - Stop and drop the remote database.

13_stop_and_drop_remo.bat

PAUSE Are you sure you want to delete all the files?

"%SQLANY11%\bin32\dbstop.exe" -c "ENG=remo;DBN=remo;UID=dba;PWD=sql" -y

PAUSE Wait a bit, then

ERASE /F *.log
ERASE /F dbeng11_log_remo.txt
ERASE /F dbmlsync_log_remo*.txt
ERASE /F mlsrv.mle
ERASE /F mlsrv11_log_cons.txt
ERASE /F remo.db
ERASE /F remo.rid

PAUSE



Friday, July 8, 2011

The fRiDaY File - But not on Wednesday July 6

Are you available for a conference call on Wednesday July 5 at 10:30 AM?





SELECT TOP 1
CAST ( DATEADD ( YEAR, sa_rowgenerator.row_num, '2011-07-05' ) AS DATE ) AS "Next Wednesday July 5"
FROM sa_rowgenerator ( rstart = 1, rend = 7, rstep = 1 )
WHERE DAYNAME ( "Next Wednesday July 5" ) = 'Wednesday'
ORDER BY sa_rowgenerator.row_num;

Next Wednesday July 5
'2017-07-05'



If your only tool is an X

to breck.carter@gmail.com
subject Client-side looping from your blog

The problem: Need to find all the records where KEY is between 61000 and 63000. There probably won't be many of these, but we need to find them if they are there.

The solution (more or less):


for (int i = 79000; i <= 81000; ++i) {
String sql = "select ... from ... where KEY = " + i;
ResultSet rs = conn.executeQuery(sql);
if (rs.next()) {
// save the results from this record
}
}

Not even the decency to use a PreparedStatement.

Unsurprisingly, this is a fair bit slower than just doing

select ... from ... where KEY between 79000 and 81000

and looping through the results.

[In the programmer's defense, the actual situation was a bit more complex than as seen here, but the fundamental issues boiled down to the above]

Net result of fixing this is that one of our major screens went from 3+ seconds to < 500 ms.



from breck.carter@gmail.com
subject Re: Client-side looping from your blog

Ack! ...that's amazing!

I absolutely positively know how "the actual situation was a bit more complex" in the real world, however. Developers often have other things on their mind, rather than performance... and in 80% (90%? 99%?) of the cases their code performs adequately regardless of the technique used. It's the other 1% that allows folks like me (and you) to be heroes when we fix it :)

But a client-side WHERE clause? I've written about it (point 22 in How To Make SQL Anywhere Slow) but it's not something you see very often. Point 22 came from Glenn Paulley, not me.

I guess it's another case of "If your only tool is an X, everything looks like a Y" where (X ,Y) = (Java compiler, Java method) instead of (database server, SQL script) like in Everything Looks Like a Database.



to breck.carter@gmail.com
subject Re: Client-side looping from your blog

Technically speaking, I wouldn't call this a client-side where clause. To me, a client-side where clause would be fetching a bunch of stuff you don't need, and then weeding through the results on the client side. In this example, you know what you need up front, and you insist on getting it through 2,000 individual queries, which is WAAAAY slower than one big query.

Your How to Make SA Slow is awesome. I hadn't seen that before. I've seen (and probably done myself) #2 (all our DBs are 32K), #3, #4 (accidentally), #13, #18 (almost all of our keys!), #23, #26, #33, #34, and probably others. Ouch.

BTW, the most awesome (in a good way) X/Y example I've seen in a long time is Eric Farrar's glider gun.


Thursday, July 7, 2011

The Thursday Quote - Michael Stonebraker


"... any programmer seriously interested in performance runs transactions using a stored-procedure interface, rather than SQL commands over ODBC/JDBC."
- 10 Rules for Scalable Performance in ‘Simple Operation’ Datastores, by Michael Stonebraker and Rick Cattell, Communications of the ACM, June 2011

That quote is taken slightly out of context from a long but very readable and fascinating article that I'm reading for the second time (I said the article is readable and fascinating, I didn't say it was shallow).

Here's what it says to me: Since many application developers working with frameworks have lost (or never had) the ability to write serious SQL, and therefore couldn't code a stored procedure to save their lives, they are doomed if they develop a serious interest in database performance.

Here are some snippets to show the quote in context (the bolding is mine):

The relational model of data was proposed in 1970 by Ted Codd as the best solution for the DBMS problems of the day — business data processing. Early relational systems included System R and Ingres, and almost all commercial relational DBMS (RDBMS) implementations today trace their roots to these two systems. As such, unless you squint, the dominant commercial vendors — Oracle, IBM, and Microsoft — as well as the major open source systems — MySQL and PostgreSQL — all look about the same today; we term these systems general-purpose traditional row stores, or GPTRS, sharing the following features:
  • Disk-oriented storage;

  • Tables stored row-by-row on disk, hence, a row store;

  • B-trees as the indexing mechanism;

  • Dynamic locking as the concurrency-control mechanism;

  • A write-ahead log, or WAL, for crash recovery;

  • SQL as the access language; and

  • A “row-oriented” query optimizer and executor, pioneered in System R.
. . .
Rule 2. High-level languages are good and need not hurt performance. Work in a SQL transaction can include the following components:
  • Overhead resulting from the optimizer choosing an inferior execution plan;

  • Overhead of communicating with the DBMS;

  • Overhead inherent in coding in a high-level language;

  • Overhead for services (such as concurrency control, crash recovery, and data integrity); and

  • Truly useful work to be performed, no matter what
. . .
The second source of overhead is communicating with the DBMS. For security reasons, RDBMSs insist on the application being run in a separate address space, using ODBC or JDBC for DBMS interaction. The overhead of these communication protocols is high; running a SQL transaction requires several back-and-forth messages over TCP/IP. Consequently, any programmer seriously interested in performance runs transactions using a stored-procedure interface, rather than SQL commands over ODBC/JDBC. In the case of stored procedures, a transaction is a single over-and-back message. The DBMS further reduces communication overhead by batching multiple transactions in one call. The communication cost is a function of the interface selected, can be minimized, and has nothing to do with the language level of the interaction.
. . .
The quote is still slightly out of context because the article isn't just talking about conversational SQL versus stored procedures, but the authors do repeat the point in a later section...

. . .
A conventional disk-based DBMS clearly spends the overwhelming majority of its cycles on overhead activity. To go a lot faster, the DBMS must avoid all the overhead components discussed here; for example, a main memory DBMS with conventional multithreading, locking, and recovery is only marginally faster than its diskbased counterpart. A NoSQL or other database engine will not dramatically outperform a GPTRS implementation, unless all these overhead components are addressed or the GPTRS solution has not been properly architected (by, say, using conversational SQL rather than a compiled stored procedure interface).
. . .

Wednesday, July 6, 2011

Product Suggestion: Allow parameter-name = expression in function calls

Both the CALL statement and the FROM clause allow the following syntax for a procedure call (although that fact is not apparent from the latter Help topic:


procedure-name ( parameter-name = expression , ... )

That might be a legacy holdover from The Time Before SQL Anywhere, but like beer is a holdover from The Time Before The Industrial Revolution, it is a good thing. Consider a call to sa_rowgenerator... suppose the author wishes to make sure the reader understands exactly what the call is doing without appealing to the docs...

SELECT * FROM sa_rowgenerator ( 1, 7, 1 )

Go ahead, without looking it up, tell me what that does.

... I'll wait.

... waiting.



Here's the answer:

SELECT * FROM sa_rowgenerator ( rstart = 1, rend = 7, rstep = 1 )

row_num
1
2
3
4
5
6
7

The rules for expressions should be extended to allow the same thing, instead of throwing Syntax error near '=' on line 1:

CREATE FUNCTION function_name (
parameter_1 INTEGER,
parameter_2 INTEGER )
RETURNS INTEGER
BEGIN
RETURN parameter_1 + parameter_2;
END;

SELECT function_name ( parameter_1 = 2, parameter_2 = 2 );

Of course, authors who don't care about the reader (and they are in the vast majority) would not need to use this syntax.

(Yes, the ambiguity was intended... both readers, and authors who don't care, vastly outnumber authors who care... I see the code every day.)

Does it already work in SQL Server?


What do you think?

Let's see... of course not!

1> CREATE FUNCTION function_name (
2> @parameter_1 INTEGER,
3> @parameter_2 INTEGER )
4> RETURNS INTEGER
5> BEGIN
6> RETURN @parameter_1 + @parameter_2;
7> END
8> GO
CREATE FUNCTION function_name (
@parameter_1 INTEGER,
@parameter_2 INTEGER )
RETURNS INTEGER
BEGIN
RETURN @parameter_1 + @parameter_2;
END
1>
2> SELECT dbo.function_name ( @parameter_1 = 2, @parameter_2 = 2 )
3> GO
Msg 137, Level 15, State 2, Server ENVY, Line 2
Must declare the scalar variable "@parameter_1".

Just to prove it wasn't a problem with the CREATE FUNCTION...

1> SELECT dbo.function_name ( 2, 2 )
2> GO
SELECT dbo.function_name ( 2, 2 )

-----------
4

(1 row affected)


Monday, July 4, 2011

Latest SQL Anywhere EBFs: 11.0.1.2622 Windows, 10.0.1.4239 Linux

Please note: Not all EBFs appear on the Sybase website on the dates listed for them... sometimes they appear later... much later. The three asterisks "***" indicate what's new about this posting compared with the previous one ...and they appear here when I get around to checking :)

Caveat Emptor: Only the latest point releases of the three supported versions are shown here: 12.0.1, 11.0.1 and 10.0.1. For example, 12.0.0.2687 was "released" on 21 Jun 2011 but it doesn't appear here; only 12.0.1 EBFs are shown... if you're using Version 12 you should be on 12.0.1 :)

Having said that: Versions 5.5 and 9.0.2 are still wonderful things, you just won't be seeing any more fixes. Versions 7 and 8, well, not so much, and the less said about Version 6 the better :)

Who knew?...that so much could be said about EBFs? Oh, yeah... to find out what you're running just do SELECT @@VERSION

Current builds...

HP-UX Itanium 11.0.1.2584 EBF 07 Apr 2011
10.0.1.3777 EBF 14 Oct 2008

HP-UX PA-RISC 10.0.1.3778 EBF 16 Oct 2008

IBM AIX 12.0.1 GA Upgrade from 12.0.0 15 Mar 2011
11.0.1.2584 EBF 07 Apr 2011

Linux Itanium 10.0.1.3777 EBF 14 Oct 2008

Linux x86/x64 12.0.1.3311 EBF 01 Apr 2011
11.0.1.2617 EBF 13 Jun 2011
10.0.1.4239 EBF *** 17 Jun 2011 ***

Mac OS 12.0.1.3352 EBF 26 May 2011
11.0.1.2449 EBF 29 Jun 2010
10.0.1.4042 EBF 01 Oct 2010

NetWare 10.0.1.4036 EBF 10 Mar 2010

Solaris SPARC 12.0.1 GA Upgrade from 12.0.0 15 Mar 2011
11.0.1.2588 EBF 19 Apr 2011
10.0.1.4239 EBF 17 Jun 2011

Solaris x64 11.0.1.2584 EBF 07 Apr 2011
10.0.1.3870 EBF 01 Apr 2009

Windows CE 10.0.1.4036 EBF 09 Mar 2010

Windows Itanium 10.0.1 GA Upgrade from 10.0.0 26 Apr 2007

Windows x86/x64 12.0.1.3356 EBF 17 Jun 2011
11.0.1.2622 EBF *** 21 Jun 2011 ***
10.0.1.4239 EBF 17 Jun 2011


Friday, July 1, 2011

The fRiDaY File - Channeling Gary Larson

I would love to show one of the Far Side pasture scenes side-by-side with Mordac's cow, but Gary Larson says "no". To see the Larson cows, you have to buy a book like this or maybe one of these.

Dilbert.com


Dumping stored procedure source code into a text file

All code, nothing but code... here's a single SQL statement that will write the source code for all of your SQL Anywhere stored procedures into a single text file:


UNLOAD
SELECT STRING (
'----------------------------------------------------------------\x0d\x0a-- ',
SYSPROCEDURE.proc_name,
'\x0d\x0a----------------------------------------------------------------\x0d\x0a',
COALESCE ( SYSPROCEDURE.source, SYSPROCEDURE.proc_defn ),
'\x0d\x0a\x0d\x0a' )
FROM SYSPROCEDURE
WHERE USER_NAME ( SYSPROCEDURE.creator ) = 'DBA'
ORDER BY SYSPROCEDURE.proc_name
TO 'c:/temp/procedures.sql'
DELIMITED BY ''
ESCAPES OFF
HEXADECIMAL OFF
QUOTES OFF;
  • The UNLOAD statement on lines 1 and 11 through 15 is wrapped around the SELECT on lines 2 through 10.

  • The STRING call on lines 2 through 7 builds a character string containing the source code for one procedure, plus some line breaks and "-- header text".

  • The COALESCE call on line 6 chooses which version of the source code to use. SYSPROCEDURE.source contains the code the way you wrote it if the preserve_source_format database option was 'On' when the procedure was last saved, and SYSPROCEDURE.proc_defn must be used if preserve_source_format was 'Off'.
    Note: The code in SYSPROCEDURE.proc_defn is a completely mangled version of slightly modified from the code you wrote. Here's how to ensure that your original code is always available in SYSPROCEDURE.source:

    SET OPTION PUBLIC.preserve_source_format = 'On';

  • The TO clause on line 11 specifies where the file goes. This filespec is relative to where the database server is running, not where dbisql is running, so it's often safest to include the entire drive and path. SQL Anywhere interprets the forward slash "/" in a file specification string as a Windows backslash "\". If you want to code backslashes in a string literal then double them up because "\" is an escape character; in this case use 'c:\\temp\\procedures.txt' to be safe.

  • The four clauses on lines 12 through 15 force the UNLOAD statement to obey the rule "what you have is what you get". In other words, the contents of the STRING() are written to the text file without any reformatting, no 'quotes', no \escape characters, no \0x hex numbers, no messin' around with my data! Not every one of these four clauses are necessary in every "what you have is what you get" context, but they never hurt... I'm not sure which ones are necessary here, and I don't give [an expletive deleted] care.
Here's a bit of the output from the Foxhound database:

----------------------------------------------------------------
-- f_forward_string_function_call
----------------------------------------------------------------
create FUNCTION f_forward_string_function_call (
IN @p_remote_server_name VARCHAR ( 128 ),
IN @p_string_function_call LONG VARCHAR )
RETURNS LONG VARCHAR
NOT DETERMINISTIC
BEGIN
... [other stuff]
END
... [lots and lots of other stuff]
----------------------------------------------------------------
-- rroad_verify_session_id
----------------------------------------------------------------
create PROCEDURE rroad_verify_session_id (
IN @p_session_id VARCHAR ( 36 ),
OUT @p_error_message LONG VARCHAR )
BEGIN
... [other stuff]
END

The UNLOAD SELECT code is easy to customize, to
  • change the format of the "-- header text", or eliminate the headers altogether,

  • include the owner name in the output if more than one procedure has the same name [censored] (polymorphism sucks big wampum, but that's not politically correct so I can't say that) [/censored],

  • change the WHERE clause to SELECT different subsets of procedures,

  • include the COMMENT ON procedure text if you have any,

  • set APPEND ON instead of OFF, and/or

  • write each procedure to a separate file.

Huh? You call that EASY?


Well, maybe that last one isn't quite as easy as the others, so here goes...

FOR f_fetch AS c_fetch INSENSITIVE CURSOR FOR
SELECT SYSPROCEDURE.proc_name AS @procedure_name,
STRING (
'----------------------------------------------------------------\x0d\x0a-- ',
SYSPROCEDURE.proc_name,
'\x0d\x0a----------------------------------------------------------------\x0d\x0a',
COALESCE ( SYSPROCEDURE.source, SYSPROCEDURE.proc_defn ),
'\x0d\x0a\x0d\x0a' ) AS @procedure_source_code,
STRING ( 'c:/temp/procedures/', @procedure_name, '.sql' ) AS @output_filespec
FROM SYSPROCEDURE
WHERE USER_NAME ( SYSPROCEDURE.creator ) = 'DBA'
ORDER BY SYSPROCEDURE.proc_name
FOR READ ONLY
DO
MESSAGE STRING ( 'Writing ', @procedure_name ) TO CLIENT;
UNLOAD SELECT @procedure_source_code
TO @output_filespec
DELIMITED BY ''
ESCAPES OFF
HEXADECIMAL OFF
QUOTES OFF;
END FOR;
MESSAGE 'Done' TO CLIENT;

Writing f_forward_string_function_call
...
Writing rroad_verify_session_id
114 row(s) affected
Execution time: 0.23 seconds
Done
Execution time: 0.019 seconds

Actually, that was pretty easy... the FOR loop rocks! ...it beats the snot stuffing out of DECLARE CURSOR, DECLARE local variables, OPEN cursor, FETCH INTO all those variables, IF SQLCODE LEAVE or WHILE @@FETCH_STATUS or DECLARE EXCEPTION, CLOSE cursor, all that cursor loop carp you have to use in Transact SQL... not to mention worrying about CLOSE_ON_ENDTRANS (oh, you forgot about that one, didn't you? :)

PS: If you are wondering about @procedure_source_code's data type (and you should be), it's LONG VARCHAR according to the EXPRTYPE function (which also rocks! EXPRTYPE, and being able to use LONG VARCHAR for local variables, both of them rock!)


Wednesday, June 29, 2011

Top Ten Things To Know About Sybase Technologies and Their Fit Within SAP

That's the name of a session at the SAP TechEd conference which is co-located with Sybase Techwave in Las Vegas on September 12 through 16:


Top Ten Things To Know About Sybase Technologies and Their Fit Within SAP

Lecture (1 hour)

This session will introduce you to the Sybase technology solution portfolio, now integrated within many SAP solutions and available to the broader SAP community. Sybase's strategy of the unwired enterprise, long focused on the core aspects of managing data, analyzing data, and mobilizing data, has many current and planned integration points within the SAP technology portfolio to drive operational efficiency and reduce overall costs. This session will give answers to questions such as: What is the overall strategy of the Sybase technology solution portfolio and how does it integrate into the SAP portfolio? How will information management and business intelligence technology evolve in the context of a combined offering from SAP, BusinessObjects and Sybase? Get insights and guidance on what you need to know now, and what you need to anticipate coming in the future of Sybase's technology solution portfolio.

Session Level: Beginner

SAP Products Type: Sybase Adaptive Server Enterprise ; Sybase IQ ; Sybase Replication Server ; Sybase Unwired Platform

Topics: Enterprise Data Warehousing ; Enterprise Information Management (EIM) ; On Demand ; On Device ; Sybase Technology and Solutions ; Sybase, an SAP Company

Job Function: Business Process ; Business Intelligence ; Design and Architecture ; IT Management ; SAP Partners, Consultants and ISVs ; Web and Application Development ; Systems Administration and Security

Speaker(s):
Peter Thawley
Sr. Director / Architect, Office of the CTO
Sybase, an SAP Company

What else?


So far there are 256 sessions (yes, a perfect power of 2) listed for TechEd, and a completely wonderful web interface for searching the sessions:



Have a look for yourself, it's really quite breathtaking when you think of past Techwave agendas, at least in the number of sessions and the absolute horde great variety of speakers.

In other news...


The "TechWave 2011 Partial List of Breakout Sessions" has appeared on the Sybase Techwave 2011 website.

Also, Techwave registration is now open.


Monday, June 27, 2011

Why did it take 0.43 seconds to insert one row?

Jonathan O'Keeffe (jpo) posted this comment on Stupid Error Messages last week:

"Why did it take 0.43 seconds to insert one row? Is there something special about uniqueidentifier that makes this slow?"
The short answers are "it doesn't" and "no".

Jonathan was referring to this output copied from the "SQL Statements" and "Results - Messages" panes displayed by SQL Anywhere's dbisql utility:

CREATE TABLE t (
pkey UNIQUEIDENTIFIER NOT NULL PRIMARY KEY );

INSERT t VALUES ( '4b67abbe3c9246f883db0ab2d2b78f8b' );

Execution time: 0.43 seconds
1 row(s) inserted

I remember thinking at the time "That's an awfully big number!" but I left it in because that's what was actually displayed.

Or was it?


I can't remember (or find) the database where I ran that test, or even remember what version of SQL Anywhere was used.

But wait, it gets worse... I can't reproduce anything that looks even remotely close to that output. The order of the "Execution time:" and "1 row(s) inserted" lines in versions 9 and 10 is different from versions 11 and 12, but in all four versions there are two "Execution time:" numbers displayed because there are two statements executed, CREATE TABLE and INSERT:



Here is the long answer to Jonathan's questions...
  • The "Execution time: 0.43 seconds" is probably a copy-and-paste mistake. It might be for the CREATE TABLE... it could also be for the INSERT, but it probably isn't.

  • No, there is nothing special about the UNIQUEIDENTIFIER data type that makes it slow... under the covers it's just another steenking BINARY string.

  • As far as I know (can't find it in the docs) the "Execution time:" number is a notoriously unreliable client-side view of how long something takes.

  • No matter how fast the client-server interface is, it adds considerable overhead (relatively speaking) to very fast operations like INSERT. For example, if you have a million rows to insert and you care about performance, don't code a client-side loop that sends a million separate INSERT statements to the server. Use INSERT SELECT, call a stored procedure, use LOAD TABLE, do something to push the process down to the server.
    "Any programmer seriously interested in performance runs transactions using a stored-procedure
    interface, rather than SQL commands over ODBC/JDBC."
    - Michael Stonebraker

  • Execution times can vary wildly with the state of the server, especially if (like in this case) absolutely no care has been taken with experimental design ...in fact, in this case, there was no experiment being conducted.



Friday, June 24, 2011

The fRiDaY File - Managing the Web-Enabled Enterprise

Dilbert.com


Will SUP 2.1 Mark The Beginning Of The End For SQL Anywhere?

Perhaps this is just another crazy conspiracy theory but here goes...

When version 2.1 of the Sybase Unwired Platform is released in Septemer 2011, it will ship with an HTML5 container, plus 16 mobile applications using the HTML5 container, and according to Kevin Benedict "HTML5 will play a significant and growing role in the SAP/Sybase strategy and road map".

So what?


Here's so what: One of the top 5 supported features in HTML5 is Web Storage which is persistent storage of data on the client side. In other words, a database.

Just like SQL Anywhere, except it isn't.

Or is it?

Well, if you do a Google search on HTML5 "SQL Anywhere" you pretty well come up empty, so if SQL Anywhere has a role to play with SUP 2.1's HTML5 container it's a well kept secret.



If you do a Google search for "SUP 2.1" you will see that it is already available... but not from SAP/Sybase. I see a name change coming in the near future, or a trademark lawsuit... or maybe just a good old-fashioned Googlewashing campaign :)


Wednesday, June 22, 2011

Comparing SQL Anywhere With ASE

Glenn Paulley has just published a wonderful new whitepaper describing the differences between SQL Anywhere and Adaptive Server Enterprise.

Here's the official description:

Migrating SQL Anywhere database applications to ASE. Some Sybase customers may need to explore the possibility of migrating one or more SQL Anywhere applications to ASE servers to consolidate their server environments, or to take advantage of some specific ASE product features. This white paper outlines the major differences between the two systems, specifically comparing SQL Anywhere 12.0.1 with ASE 15.5.
Here's how Dilbert might describe it:
To be Forewarned is to be Forearmed. Some SQL Anywhere developers may want to know how miserable what life will be like when they are forced by management to move their databases to ASE. This white paper outlines the major differences in complexity between the two systems, specifically comparing SQL Anywhere 12.0.1 with ASE 15.5.

Dilbert.com
But Glenn isn't Dilbert... you won't find any sarcasm in his whitepaper, just the facts. Pages and pages of facts... 71 pages in all.


Monday, June 20, 2011

Latest SQL Anywhere EBFs: 12.0.1.3356 and 10.0.1.4239 Windows, 10.0.1.4239 Solaris

Current builds...

HP-UX Itanium 11.0.1.2584 EBF 07 Apr 2011
10.0.1.3777 EBF 14 Oct 2008

HP-UX PA-RISC 10.0.1.3778 EBF 16 Oct 2008

IBM AIX 12.0.1 GA Upgrade from 12.0.0 15 Mar 2011
11.0.1.2584 EBF 07 Apr 2011

Linux Itanium 10.0.1.3777 EBF 14 Oct 2008

Linux x86/x64 12.0.1.3311 EBF 01 Apr 2011
11.0.1.2617 EBF 13 Jun 2011
10.0.1.4213 EBF 28 Apr 2011

Mac OS 12.0.1.3352 EBF 26 May 2011
11.0.1.2449 EBF 29 Jun 2010
10.0.1.4042 EBF 01 Oct 2010

NetWare 10.0.1.4036 EBF 10 Mar 2010

Solaris SPARC 12.0.1 GA Upgrade from 12.0.0 15 Mar 2011
11.0.1.2588 EBF 19 Apr 2011
10.0.1.4239 EBF *** 17 Jun 2011 ***

Solaris x64 11.0.1.2584 EBF 07 Apr 2011
10.0.1.3870 EBF 01 Apr 2009

Windows CE 10.0.1.4036 EBF 09 Mar 2010

Windows Itanium 10.0.1 GA Upgrade from 10.0.0 26 Apr 2007

Windows x86/x64 12.0.1.3356 EBF *** 17 Jun 2011 ***
11.0.1.2606 EBF 20 May 2011
10.0.1.4239 EBF *** 17 Jun 2011 ***


Friday, June 17, 2011

The fRiDaY File - Elbonian to English

What would you do if you saw this error message?

Cannot drop the table '#elbat_yraropmet_ymmud_a_si_siht' 
Would you try this?
Nice try, but here is where you need to look.


Stupid Error Messages

Have you ever seen an error message like this one?

Invalid phone number (212) 555-1284 - not numeric
Maybe this one?
Invalid phone number 2125551284 - format not (999) 999-999
Both messages are stupid, stupid, stupid... that's three "stupids" out of a maximum possible score of 5. Modern systems should be able to deal with human-readable input formats like credit card numbers with or without the dashes.
Invalid credit card number 4533-3048-3842-9544 - not numeric

Today's Stupid Error Message



CREATE TABLE t (
pkey UNIQUEIDENTIFIER NOT NULL PRIMARY KEY );

INSERT t VALUES ( '4b67abbe3c9246f883db0ab2d2b78f8b' );
GO

Msg 8169, Level 16, State 2, Server ENVY, Line 4
Conversion failed when converting from a character string to uniqueidentifier.

You need the dashes for that when using SQL Server 2008:

INSERT t VALUES ( '4b67abbe-3c92-46f8-83db-0ab2d2b78f8b' );
GO

(1 row affected)

I suppose we should be grateful it accepts lower case.

For the record, SQL Anywhere has no such difficulty:

CREATE TABLE t (
pkey UNIQUEIDENTIFIER NOT NULL PRIMARY KEY );

INSERT t VALUES ( '4b67abbe3c9246f883db0ab2d2b78f8b' );

Execution time: 0.43 seconds
1 row(s) inserted

As they say, Watcom does things they way they should be done.


Monday, June 13, 2011

The MESSAGE Statement In SQL Server

Question: How do I write diagnostic/trace messages to a text file during the execution of a stored procedure on Microsoft SQL Server 2008, similar to SQL Anywhere's MESSAGE ... TO CONSOLE statement?

I need this facility to help diagnose and test MSS stored procedures called from MobiLink scripts.

Answer: When this question was asked on the SQL Anywhere Forum, three answers were given:

I chose the first answer: Call SQL Server's xp_cmdshell procedure to execute a Windows ECHO statement which uses the ">>" redirection operator to append a string to a file.

Here's the code, warts and all:

-- To allow advanced options to be changed.
EXEC sp_configure 'show advanced options', 1
GO
-- To update the currently configured value for advanced options.
RECONFIGURE
GO
-- To enable the feature.
EXEC sp_configure 'xp_cmdshell', 1
GO
-- To update the currently configured value for this feature.
RECONFIGURE
GO

BEGIN TRY
DROP PROCEDURE write_diagnostic_message
END TRY
BEGIN CATCH
-- ignore error
END CATCH
GO

CREATE PROCEDURE write_diagnostic_message
@message VARCHAR ( 1000 )
AS
BEGIN
DECLARE @cmd VARCHAR ( 2000 )
SET NOCOUNT ON
SET @cmd = 'ECHO ' + CONVERT ( VARCHAR, CURRENT_TIMESTAMP, 121 ) + ' ' + @message + ' >>c:\temp\messages.txt'
EXEC master..xp_cmdshell @cmd, no_output
END
GO

The crap stuff on lines 1 through 12 is necessary to prevent xp_cmdshell from failing with an error message.

The BEGIN TRY and BEGIN CATCH blocks on lines 14 through 20 are a quick-and-dirty way to execute a statement (in this case DROP PROCEDURE) which may or may not work and you don't care which.

The procedure defined on lines 22 through 31 provides a slightly-easier-to-call interface to the xp_cmdshell solution:
  • The DECLARE on line 26 is necessary because the EXEC statement can't handle expressions passed as arguments to stored procedures.

  • The SET NOCOUNT ON on line 27 is more crap stuff required by SQL Server; you can read about it in the "Best Practices" section of the page documenting the CREATE PROCEDURE statement.

  • The SET statement on line 28 uses CONVERT to append the current date and time in front of the @message parameter, and builds an ECHO ... >>filespec command."

  • The EXEC statement on line 29 passes the ECHO command to xp_cmdshell, and the no_output option prevents xp_cmdshell itself from trying to display anything on screen.
Here's how to code a call to write_diagnostic_message from another SQL Server procedure:

EXEC write_diagnostic_message 'This is a test'

Here's the output in c:\temp\messages.txt:

2011-06-13 06:00:59.897 This is a test

Caution: Don't try to pass an expression as the argument to write_diagnostic_message, that won't work in SQL Server. You'll need a DECLARE another variable and use SET to fill it from the expression, and then pass the variable to write_diagnostic_message.

Question: Why do people use SQL Server after using SQL Anywhere?

Answer: Because they are forced to by management? ...it's a continuing mystery to me.


Friday, June 10, 2011

The fRiDaY File - Look! Actual Code!

Dilbert.com


Opinion: Code Generators Are (Should Be) Disposable

A code generator is a piece of software, often template-driven, which creates a large amount of application program code from a small amount of information entered by a programmer. The generated code is then processed by a compiler or other language processor just as if it was written by a human being.

(This definition is not to be confused with the code generation component of a true language compiler... that's something else entirely.)
Code generators are wonderful things, especially when you have a lot of boring, similar code to write. The MobiLink Synchronization Model Wizard is one example of a valuable code generator, especially the most recent version that comes in-the-box with SQL Anywhere Version 12; MobiLink setups often include hundreds of scripts that are exactly the same except for differences between one table and another (columns, primary keys, etcetera).

When In Rome


By definition, no part of a code generator is present at runtime. That means runtime error messages are expressed in the context of the output created by the code generator, not the input created by the programmer. In the case of the MobiLink wizard, error messages are expressed in terms of the output SQL, not the spreadsheet-like input to the wizard.

And that's the main reason a code generator should be disposed of as soon as possible, right after all the boring bits have been successfully generated. If the debugging is going to involve the generated code, then the fixes should be made to the generated code... When in Rome, do as the Romans do... When in SQL, code in SQL.

This is reinforced by the fact that special cases are always going to be difficult if not impossible to express as input to a code generator. No matter how sophisticated a code generator is, there are going to be cases that have to be hand-coded in the output language, not the input.

A worst-case scenario involves a custom-coded code generator, especially one created and used by a temporary contractor... those really should be disposed of as soon as their usefulness diminishes. There will always be more people who can work with the output code than those who can maintain and operate the code generator.


Wednesday, June 8, 2011

Most Successful Database Software Vendor In China


According to USA Today
Sybase CEO John Chen is "one of the most knowledgeable American business leaders on the inner workings of China."

And it looks like it's paying off...

Sybase Named Most Successful Database Software Vendor in China

China's Centre for Information Industry Development Consulting (CCID Consulting) honoured the company with the "2010-2011 Most Successful Database Software Vendor in China" award. Sybase is the only enterprise vendor to receive this prestigious award two years in a row.

... CCID Consulting estimated Sybase's database has garnered 25.1 percent market share in the telecommunications industry, 25 percent in the banking industry, and 24.1 percent in the insurance industry.

... These recognitions reaffirm the technical prowess of Sybase's solutions and the excellent market competitiveness it accords to customers of its technology, including its industry-leading SQL Anywhere mobile and embedded database technology, ...


Monday, June 6, 2011

Installing A SQL Server Database From A BAK File

Question: I've just been given a SQL Server 2005 *.bak file to use as a consolidated database in a MobiLink synchronization setup... what do I do with this file?

Answer: That file was most likely created by the SQL Server 2005 BACKUP statement and it's easy to restore into a brand-new database.

But first, you need to install SQL Server. This blog is about SQL Anywhere, not SQL Server, so this article makes no assumptions about where you're starting from... like the man said on TV "nobody needs a clue":

If you want, you can start with SQL Server 2008 instead of 2005 and still use the *.bak file you've been given; you can download Microsoft SQL Server 2008 Express Edition Service Pack 1 here.

Step 2: Start the SQL Server interactive SQL utility osql.exe on the master database (which always exists on SQL Server); here's a command line for Windows:

"C:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe" -S Envy -U sa -P j68Fje9#fyu489 -d master



Step 3: Use the CREATE DATABASE statement in osql to prepare a database to receive the data in the *.bak file; the DROP DATABASE statement is included in case you have to start over again later:

USE master
GO

DROP DATABASE mydb
GO

CREATE DATABASE mydb
ON
( NAME = mydb,
FILENAME = 'C:\temp\mydb.mdf',
SIZE = 300MB )
LOG ON
( NAME = mydb_log,
FILENAME = 'C:\temp\mydb.ldf',
SIZE = 100MB )
GO

Step 4: Find out what you have in the *.bak file with the RESTORE FILELISTONLY statement:

RESTORE FILELISTONLY
FROM DISK = 'C:\temp\mydb.bak'
GO

Here are the results; only the two "LogicalName" values are important: myXXX for the database file and myXXX_log for the transaction log:

LogicalName
PhysicalName
Type
FileGroupName
Size MaxSize FileId
CreateLSN DropLSN
UniqueId ReadOnlyLSN
ReadWriteLSN BackupSizeInBytes SourceBlockSize
FileGroupId LogGroupGUID
DifferentialBaseLSN DifferentialBaseGUID
IsReadOnly IsPresent TDEThumbprint
myXXX
D:\SQLServer\Data\myXXX.mdf
D
PRIMARY
209715200 35184372080640 1
0 0
9DF4176F-4C1F-4198-A32C-3E5E91F8EED8 0
0 146210816 512
1 NULL
3619000000786000140 062CF42D-5C45-47F4-8ADB-84EF794C2F82
0 1 NULL
myXXX_log
E:\SQLServer\Logfiles\myXXX_log.ldf
L
NULL
214433792 2199023255552 2
0 0
47EE2F14-8CB0-49FA-B525-CD79D38FAE91 0
0 0 512
0 NULL
0 00000000-0000-0000-0000-000000000000
0 1 NULL
(2 rows affected)

Step 5: Use the RESTORE DATABASE statement to copy the data from the *.bak file into your database. The WITH MOVE options tell SQL Server to put the data into files that are in different locations from the original, and the WITH REPLACE option tells SQL Server accept the fact you're changing the database name to mydb. The only place you have to use the old names is in the two MOVE clauses: MOVE 'myXXX' and MOVE 'myXXX_log' refer to the LogicalName values you found in Step 4.

RESTORE DATABASE mydb
FROM DISK = 'C:\temp\mydb.bak'
WITH MOVE 'myXXX' TO 'C:\temp\mydb.mdf',
MOVE 'myXXX_log' TO 'C:\temp\mydb.ldf',
REPLACE
GO

The RESTORE DATABASE statement is remarkably robust when it comes to upgrading data on the fly; between SQL Server 2005 and 2008 there were apparently quite a few version changes:

Processed 17848 pages for database 'mydb', file 'myXXX' on file 1.
Processed 2 pages for database 'mydb', file 'myXXX_log' on file 1.
Converting database 'mydb' from version 611 to the current version 655.
Database 'mydb' running the upgrade step from version 611 to version 621.
Database 'mydb' running the upgrade step from version 621 to version 622.
Database 'mydb' running the upgrade step from version 622 to version 625.
Database 'mydb' running the upgrade step from version 625 to version 626.
Database 'mydb' running the upgrade step from version 626 to version 627.
Database 'mydb' running the upgrade step from version 627 to version 628.
Database 'mydb' running the upgrade step from version 628 to version 629.
Database 'mydb' running the upgrade step from version 629 to version 630.
Database 'mydb' running the upgrade step from version 630 to version 631.
Database 'mydb' running the upgrade step from version 631 to version 632.
Database 'mydb' running the upgrade step from version 632 to version 633.
Database 'mydb' running the upgrade step from version 633 to version 634.
Database 'mydb' running the upgrade step from version 634 to version 635.
Database 'mydb' running the upgrade step from version 635 to version 636.
Database 'mydb' running the upgrade step from version 636 to version 637.
Database 'mydb' running the upgrade step from version 637 to version 638.
Database 'mydb' running the upgrade step from version 638 to version 639.
Database 'mydb' running the upgrade step from version 639 to version 640.
Database 'mydb' running the upgrade step from version 640 to version 641.
Database 'mydb' running the upgrade step from version 641 to version 642.
Database 'mydb' running the upgrade step from version 642 to version 643.
Database 'mydb' running the upgrade step from version 643 to version 644.
Database 'mydb' running the upgrade step from version 644 to version 645.
Database 'mydb' running the upgrade step from version 645 to version 646.
Database 'mydb' running the upgrade step from version 646 to version 647.
Database 'mydb' running the upgrade step from version 647 to version 648.
Database 'mydb' running the upgrade step from version 648 to version 649.
Database 'mydb' running the upgrade step from version 649 to version 650.
Database 'mydb' running the upgrade step from version 650 to version 651.
Database 'mydb' running the upgrade step from version 651 to version 652.
Database 'mydb' running the upgrade step from version 652 to version 653.
Database 'mydb' running the upgrade step from version 653 to version 654.
Database 'mydb' running the upgrade step from version 654 to version 655.
RESTORE DATABASE successfully processed 17850 pages in 9.746 seconds (14.308
MB/sec).

Now you can start using your new database; here's an osql command line:

"C:\Program Files\Microsoft SQL Server\100\Tools\Binn\osql.exe" -S Envy -U sa -P j68Fje9#fyu489 -d mydb



Friday, June 3, 2011

The fRiDaY File - RTFM!

Dilbert.com