Question: How do I use SQL Anywhere's builtin functions like COMPRESS() and ENCRYPT() on the client side of an application?
Answer: The short answer is "You can't!", according to this response.
The long answer is,
- if you're willing to start an empty local database using the SQL Anywhere personal server on the same machine that's running your client application
- and you're willing to wave your arms and declare "This embedded database shall henceforth be regarded as part of the client side!"
- then yes, you can invoke COMPRESS() and ENCRYPT() without passing the data back and forth across the network to your "real" database.
First, here's an all-SQL demonstration of the four functions that are going to be invoked on the client side:
1 | <br>BEGIN<br>DECLARE @input LONG VARCHAR;<br>DECLARE @compressed LONG BINARY;<br>DECLARE @encrypted LONG BINARY;<br>DECLARE @decrypted LONG BINARY;<br>DECLARE @output LONG VARCHAR;<br>DECLARE @key LONG VARCHAR; <br><br>SET @key = '4wlPnLAs34cHUlgSVLUUbIzany3G3RkXQOjouGo';<br><br>SET @input = 'Hello, World!';<br>SET @compressed = COMPRESS ( @input, 'zip' );<br>SET @encrypted = ENCRYPT ( @compressed, @key, 'AES256' );<br>SET @decrypted = DECRYPT ( @encrypted, @key, 'AES256' );<br>SET @output = CAST ( DECOMPRESS ( @decrypted, 'zip' ) AS LONG VARCHAR );<br><br>SELECT @input, @compressed, @encrypted, @decrypted, @output;<br>END;<br>
|
The code on lines 11 through 17 compresses a string, then encrypts it, decrypts it and decompresses it to show the final output is the same as the original input:
1 | <br>@input,@compressed,@encrypted,@decrypted,@output<br>Hello, World!,0x789cf348cdc9c9d75108cf2fca495104001f9e046a,0x652c6bb5bf26f146592932352d63e0976ee8ed4b5f59f19682664d42c3a0a5d8543b4aceda8e98ae5875e0f2eef8f029,0x789cf348cdc9c9d75108cf2fca495104001f9e046a,Hello, World!<br>
|
Here's the output from a PowerBuilder application that does the same thing:

The client-side PowerBuilder code is quite a bit more verbose than the SQL. First of all, the local database must be started and a connection established; in this example the Window Open event is used to do that:
1 | <br>// Auto-start local database with auto-stop enabled.<br><br>gtr_local = CREATE u_tr_sqlca<br><br>gtr_local.DBMS = 'ODB'<br><br>gtr_local.DBParm &<br> = "ConnectString='Driver=SQL Anywhere 12;" &<br> + "UID=dba;PWD=sql;" &<br> + "START=C:\Program Files\SQL Anywhere 12\Bin64\dbeng12.exe -o C:\data\dbeng12_log_local.txt -os 10M -x none -zl -zp -zt;" &<br> + "DBF=C:\data\local.db'," &<br> + "ConnectOption='SQL_DRIVER_CONNECT,SQL_DRIVER_NOPROMPT'"<br> <br>CONNECT USING gtr_local;<br><br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', 'CONNECT USING gtr_local failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br><br>// Create a temporary table for holding blob data.<br><br>EXECUTE IMMEDIATE 'CREATE LOCAL TEMPORARY TABLE blob ( pkey INTEGER PRIMARY KEY, data LONG BINARY )'<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'CREATE failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br><br>// Insert a row for use by subsequent UPDATEBLOB statements.<br><br>INSERT INTO blob VALUES ( 1, '' )<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'INSERT failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br><br>
|
The code on lines 1 through 25 uses a DSN-less connection to auto-start the local database C:\data\local.db.
The code on lines 27 through 59 creates and initializes a local temporary table used to pass a single LONG BINARY value from step to step in the PowerBuilder application; this table is used to get around PowerBuilder's inability to handle blob values as easily as other data types.
The application uses two global variables: gtr_local maintains a separate connection to the local database and gs_key holds the key used by the ENCRYPT and DECRYPT funtions.
1 | <br>u_tr_sqlca gtr_local <br>string gs_key = '4wlPnLAs34cHUlgSVLUUbIzany3G3RkXQOjouGo'<br>
|
The u_tr_sqlca data type used to define gtr_local is a user-defined data type based on the standard PowerBuilder transaction object; in simple terms, gtr_local is a separate copy of the SQLCA communication area.
COMPRESS()
Here's the code for the "Compress" button clicked event:
1 | <br>blob lblob_compressed<br><br>// Erase the output fields.<br><br>sle_compressed.text = ''<br>sle_encrypted.text = ''<br>sle_decrypted.text = ''<br>sle_output.text = ''<br><br>// COMPRESS() the input into a local blob variable.<br><br>SELECTBLOB COMPRESS ( :sle_input.text, 'zip' )<br> INTO :lblob_compressed<br> FROM dummy<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'SELECT COMPRESS failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br><br>sle_compressed.text = "Compressed OK: length = " + String ( len ( lblob_compressed ) )<br><br>// Save the local blob variable in the local blob table.<br><br>UPDATEBLOB blob <br> SET blob.data = :lblob_compressed <br> WHERE blob.pkey = 1<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'UPDATEBLOB failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br>
|
The PowerBuilder SELECTBLOB statement on lines 12 through 15 returns the compressed value to the local variable lblob_compressed.
The UPDATEBLOB statement on lines 33 through 36 is used to store that value in the local temporary table for use in the next step... if there is no "next step", if all you need is the compressed value, then the UPDATEBLOB is not required.
Neither the local table nor the UPDATEBLOB statement should
ever be necessary, but it appears that the SELECTBLOB statement in the next PowerBuilder script can't handle a blob being passed to it as a host variable in the SELECT list as well being returned to another host variable in the INTO clause.
ENCRYPT()
The code for the "Encrypt" button clicked event is quite similar except that the SELECTBLOB statement works on the blob.data column passed from the previous script via the local temporary table rather than a host variable like :sle_input.text in the previous script:
1 | <br>blob lblob_encrypted<br><br>// ENCRYPT() the saved blob column into a local blob variable.<br><br>SELECTBLOB ENCRYPT ( blob.data, '4wlPnLAs34cHUlgSVLUUbIzany3G3RkXQOjouGo', 'AES256' )<br> INTO :lblob_encrypted<br> FROM blob<br> WHERE blob.pkey = 1<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'SELECTBLOB ENCRYPT failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br><br>sle_encrypted.text = "Encrypted OK: length = " + String ( len ( lblob_encrypted ) )<br><br>// Save the local blob variable in the local blob table.<br><br>UPDATEBLOB blob <br> SET blob.data = :lblob_encrypted <br> WHERE blob.pkey = 1<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'UPDATEBLOB failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br>
|
DECRYPT()
The code for the "Decrypt" button clicked event is almost identical, SELECTBLOB followed by UPDATEBLOB, except that it calls DECRYPT instead of ENCRYPT:
1 | <br>blob lblob_decrypted<br><br>// DECRYPT() the saved blob column into a local blob variable.<br><br>SELECTBLOB DECRYPT ( blob.data, :gs_key, 'AES256' )<br> INTO :lblob_decrypted<br> FROM blob<br> WHERE blob.pkey = 1<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'SELECTBLOB DECRYPT failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br><br>sle_decrypted.text = "Decrypted OK: length = " + String ( len ( lblob_decrypted ) )<br><br>// Save the local blob variable in the local blob table.<br><br>UPDATEBLOB blob <br> SET blob.data = :lblob_decrypted <br> WHERE pkey = 1<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'UPDATEBLOB failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br>
|
DECOMPRESS()
The code for the "Decompress" button clicked event is simpler because there is no "next step" so there's no need for a local blob variable:
1 | <br>// DECOMPRESS() the saved blob column into into the display field.<br><br>SELECT CAST ( DECOMPRESS ( blob.data, 'zip' ) AS LONG VARCHAR )<br> INTO :sle_output.text<br> FROM blob<br> WHERE blob.pkey = 1<br> USING gtr_local;<br> <br>IF gtr_local.SQLCODE <> 0 THEN<br> MessageBox ( 'Error', &<br> 'SELECT DECOMPRESS failed:' &<br> + '~r~nSQLCode = ' &<br> + String ( gtr_local.SQLCode ) &<br> + '~r~nSQLDBCode = ' &<br> + String ( gtr_local.SQLDBCode ) &<br> + '~r~n' &<br> + gtr_local.SQLErrText )<br> RETURN<br>END IF<br>
|
Many other client-side development environments suffer from similar limitations when it comes to handling blobs, different from PowerBuilder's but just as annoying. However, SQL Anywhere builtin functions that
don't involve LONG VARCHAR and LONG BINARY arguments and return values (and there are lots of those) shouldn't bump up against these annoyances, and they should be easier to implement on the client side.