Nathan Pralle - www.nathanpralle.com
Kickass Phone Rates from 3U
PLACES TO GO:  
Back to Software Print Version
Sybase Adaptive Server Enterprise (ASE)
to
Sybase Adaptive Server Anywhere (ASA)
Database Migration

Scripts.com Freshmeat The Perl Archive HotScripts

Overview
Although there are many websites and whitepapers from Sybase detailing the conversion of ASA to ASE, there is shocking little in the way of documentation about the reverse conversion. This is my attempt to put the steps I took and the things I did to convert an ASE database to ASA. My hopes are that this helps other developers.

Versions
This migration was performed using a database running under Adaptive Server Enterprise (ASE) version 12.5 engine into a new database running under Adaptive Server Anywhere (ASA) version 9.1.

Assumptions
These instructions assume that the original ASE database is running on the server with an ODBC DSN of 'ASE_DB' and a database of name 'OLD_DB'.

Dislaimer
Your mileage MAY and probably WILL vary. These steps that I had to take were for a particular database in a particular setting -- your situation might very well be drastically different. As all good DBA's know, databases have their own personality (which usually borders on being a schitzophrenic crank-buzzed gnat) that renders almost everything you do with them into a particular flavor. I am not responsible for anything these instructions might do to you, your data, or your company. These instructions are provided with NO WARRANTY, either explicit or implied. YOU TAKE FULL RESPONSIBILITY for any and all damages, real or imagined, that occur from the using of these instructions. As they say in Linux, if it breaks, you get to keep both pieces.

Format
Actual commands will be presented in fixed-width font. Comments are in variable-width.

Why Convert to ASA?

There are many reasons to make the switch to ASA. For one, ASE is an enterprise-level database -- it is large and robust and works very, very well -- for tons of users. This is not always the application that developers are looking for. In fact, they are often looking for a single workstation deployment and/or a small client-server setup, not a huge corporation-level DB server. For these applications, ASA excels.

Secondly, ASE is a whore to administer compared to ASA. There's just that much more complexity to it (because it is enterprise-level) that you pretty much need someone in-house that is fluent in DBs if not an official DBA. Many applications that developers have do not come bundled with a DBA or even someone who knows much about computers at all. For these instances, you want an engine that can just run without someone fiddling. ASA is your tool of choice in these instances.

Steps to Conversion
Create a brand-new ASA DB using Sybase Central's Create Database wizard. Standard DB, no encryption, 2048 bytes per page, and make sure you select Emulate ASE on the one screen. Create with name 'ASA_DB' and both a log file and mirrored log file (as required for your application).

Sybase has made a series of stored procedures inside ASA that should, theoretically, convert an ASE (or any other ODBC-compliant DB) to ASA in one fell swoop. That was not my experience, but YMMV.

Go into Interactive SQL for ASA and issue the following commands:

Create an external server using your ODBC setup:
CREATE SERVER ASE_REMOTE_DB CLASS 'ODBC' USING 'ASE_DB';

Create an external login to the ASE DB:
CREATE EXTERNLOGIN DBA TO ASE_REMOTE_DB REMOTE LOGIN dba_username IDENTIFIED BY password;

For The Lucky

Sybase has created one stored procedure that calls all others and does the entire migration -- assuming there are no errors. If there are errors, no telling what you might have to go through (that's why I wrote this document). If you are lucky, you will be able to issue the following command to convert tables, data, and foreign keys. Issue the following command inside Interactive SQL for ASA:

CALL sa_migrate("dba","ASE_REMOTE_DB",NULL,"dbo","OLD_DB",1,1,1);

If you're lucky, that'll work fine and you'll have no errors. If so, jump down to the part about converting stored procedures and triggers. If the gods do not shine upon thy face, continue on.

Populates a temp table with the list of tables in the ASE DB:
CALL sa_migrate_create_remote_table_list('ASE_REMOTE_DB',NULL,NULL,'ASA_DB');

Create the tables in the new ASA DB based on the above list:
CALL sa_migrate_create_tables('dba');

Migrates all data by doing SELECT and INSERTS from the ASE to the ASA DB through the proxy tables:
CALL sa_migrate_data('dba');

Populate a temp table with all the foreign keys to be migrated from ASE to ASA:
CALL sa_migrate_create_remote_fks_list('ASE_REMOTE_DB');

SPECIAL CASE:
These are a series of special steps I had to perform to get the foreign keys to migrate correctly. If this is your FIRST time running the migration, go past this red section and continue below. If you have tried the migration and it bombed, you might have to do some sleuthing and possibly do something like the below. I put these special instructions here for helpful clues.

The next step I had to do because not all tables in my ASE DB had primary keys. According to Sybase, ASE is often like Oracle in this sense -- unique constraints can prove to be better performing than primary keys, so oftentimes they are used instead of PKs. The problem is that ASA gets cranky about making a foreign key to anything that isn't a PK, so we have to do some manipulation.

Create a temp table to search for primary keys:
CREATE TABLE pkeys( table_name CHAR(256), column_name CHAR(256), column_id INT);

Create a procedure to get all primary keys for a single table. This is a modification of the sp_pkeys stored proc that comes natively with ASA. If there are no primary keys for that table, store an indicator as a placeholder so we can select them out later:

create procedure getPrimaryKeys(
		in @table_name char(256),
		in @column_name char(256))
	result(table_name char(256),
		column_name char(256),
		key_seq unsigned integer)
begin
	DECLARE err_notfound EXCEPTION FOR SQLSTATE '02000';
	DECLARE mySV CHAR(256);
	DECLARE mySC CHAR(256);
	DECLARE myTN CHAR(256);
	DECLARE myCN CHAR(256);
	DECLARE myCI INT;
	DECLARE count INT;
	DECLARE myCursor CURSOR FOR
		select table_name,
		column_name,
		column_id
		from SYS.SYSTABLE as t,
		SYS.SYSCOLUMN as c where
		t.table_id = c.table_id and
		table_name like @table_name and
		pkey in( 'Y','M');
	OPEN myCursor;
	SET count=0;
	SET mySV=@table_name;
	SET mySC=@column_name;
	MyLoop:
	LOOP
		FETCH NEXT myCursor INTO myTN,myCN,myCI;
		IF SQLSTATE=err_notfound THEN
			LEAVE myLoop;
		END IF;
		SET count=count+1;
		INSERT INTO pkeys(
			table_name,		
			column_name,
			column_id)
		VALUES(
			myTN,
			myCN,
			myCI);
	END LOOP MyLoop;
	IF COUNT=0 THEN
		INSERT INTO pkeys(
			table_name,
			column_name,
			column_id)
		VALUES(
			mySV,
			mySC,
			0);
	END IF;
	CLOSE myCursor;
END;


Create a procedure to parse through the foreign keys migration temp table and use getPrimaryKeys to find out if the primary keys really do exist as they are listed inside the FK temp table.

CREATE PROCEDURE findKeys()
BEGIN
    DECLARE my_name CHAR(256);
	 DECLARE my_column CHAR(256);
    DECLARE err_notfound EXCEPTION FOR SQLSTATE '02000';
    DECLARE myCursor CURSOR FOR 
        SELECT pk_table, pk_column
        FROM migrate_remote_fks_list;
    OPEN myCursor;
    MyLoop:
    LOOP
        FETCH NEXT myCursor INTO my_name,my_column;
        IF SQLSTATE=err_notfound THEN   
            LEAVE MyLoop;
        END IF;
        CALL getPrimaryKeys(my_name,my_column);
    END LOOP MyLoop;
    CLOSE myCursor;
END;


Call the stored procedure to find the primary keys and populate 'pkeys':
CALL findKeys();

Select out only those tables which don't have primary keys but SHOULD:
SELECT DISTINCT * FROM pkeys WHERE column_id=0 ORDER BY table_name ASC;

Now add those primary keys in by issuing ALTER TABLE statements.
Example: ALTER TABLE tablename ADD PRIMARY KEY (primary_key_column);

Drop the pkeys table, as you're done with it:
DROP TABLE pkeys;

Due to my ASE DB being messed up, it did not have any names for the foreign keys. That's a problem because Sybase's script explicitly operates on those names. No names, no conversions. This stored proc solves that problem by naming all the primary/foreign keys with the name of the column that they act upon.

CREATE PROCEDURE fixFKPKNames()
BEGIN
	DECLARE statement LONG VARCHAR;
	DECLARE err_notfound EXCEPTION FOR SQLSTATE '02000';
	DECLARE pkCol LONG VARCHAR;
	DECLARE fkCol LONG VARCHAR;
	DECLARE myID INTEGER;
	DECLARE myCursor CURSOR FOR
		SELECT 
			migrate_remote_fks_list.fk_id,
			migrate_remote_fks_list.pk_column,
			migrate_remote_fks_list.fk_column
		FROM
			migrate_remote_fks_list;
	OPEN myCursor;
	MyLoop:
	LOOP
		FETCH NEXT myCursor INTO myID,pkCol,fkCol;
		IF SQLSTATE=err_notfound THEN
			LEAVE MyLoop;
		END IF;
		set statement = 'UPDATE migrate_remote_fks_list SET '+
			migrate_remote_fks_list.pk_name='+
			pkCol+
			',migrate_remote_fks_list.fk_name='+
			fkCol;
		message statement type info to client;
		UPDATE 
			migrate_remote_fks_list 
		SET 
			migrate_remote_fks_list.pk_name=pkCol,
			migrate_remote_fks_list.fk_name=fkCol 
		WHERE 
			migrate_remote_fks_list.fk_id=myID;
	END LOOP MyLoop;
	CLOSE myCursor;
END;
Call the stored proc to fix the names.
CALL fixFKPKNames();

Delete and modify a few tables because the default data contains erroneous things that prevent FKs from being created. The code below was what I had to run:
DELETE FROM tablename;
INSERT INTO tablename (fieldlist)VALUES(datalist);

Add a unique constraint to state_province.state_province_abbrev through SybCentral. (Right-click on field, Properties)

Create the migration script to create the foreign keys. The original script (by Sybase) is called by issuing:
CALL sa_migrate_create_fks();

However, due to the keys issue (see above), I had to pull the source code of the proc and add some debug coding in it to see what was going on. The below is the resultant stored proc:

create procedure myMigrateFKS(in i_table_owner varchar(128))
begin
	declare stmt long varchar;
	declare pk_col_list long varchar;
	declare fk_col_list long varchar;
	for 
		fk as fkc dynamic scroll cursor for
			select 
				ef.fk_id as o_fk_id,
				ef.pk_table as o_pk_table,
				ef.pk_name as o_pk_name,
				ef.fk_table as o_fk_table,
				ef.fk_name as o_fk_name 
			from
				dbo.migrate_remote_fks_list as ef,
				dbo.migrate_remote_table_list as et 
			where
				ef.fk_table = et.table_name AND
				ef.created <>1
			group by 
				ef.fk_id,
				ef.pk_table,
				ef.pk_name,
				ef.fk_table,
				ef.fk_name 
	do
	IF NOT exists(SELECT 1 FROM sys.sysforeignkey WHERE role=o_fk_name)then
		select 
			list(pk_column order by key_seq asc),
			list(fk_column order by key_seq asc) 
		into 
			pk_col_list,
			fk_col_list 
		from
			dbo.migrate_remote_fks_list 
		where
			fk_table = o_fk_table and
			pk_table = o_pk_table and
			fk_name = o_fk_name;
		set stmt='PRIMARY KEY LIST: '+pk_col_list;
		message stmt type info to client;
		set stmt='FOREIGN KEY LIST: '+fk_col_list;
		message stmt type info to client;
		set stmt='ALTER TABLE '+'"'+
			i_table_owner+
			'"'+
			'.'+
			'"'+
			o_fk_table+'"'+
			' ADD FOREIGN KEY "'+
			o_fk_name+
			'" ( "'+
			fk_col_list+
			'" ) REFERENCES '+
			'"'+
			i_table_owner+
			'"."'+
			o_pk_table+
			'" ( "'+
			pk_col_list+
			'" )';
			message stmt type info to client;
			execute immediate stmt;
		ELSE
			message 'Foreign key '+o_fk_name+
			' already exists -- Skipping.' type info to client;
		END IF;
		update 
			dbo.migrate_remote_fks_list 
		set
			created = 1 
		where
			fk_name = o_fk_name;
	end for
end;
Now run the procedure to perform the migration:
CALL myMigrateFKS('dba');



Migrate the foreign keys (if you didn't have to follow the special steps above):
CALL sa_migrate_create_fks('dba');

Delete proxy tables:
CALL sa_migrate_drop_proxy_tables('dba');

Migrate Stored Procedures, Triggers, Views, and User-Defined Datatypes


USER-DEFINED DATATYPES:
There are many user-defined datatypes in the ASE DB that need to be migrated first. Do this by connecting to the ASE DB through SybCentral for ASE and finding the User-Defined Datatypes folder under the ASA DB. Select all UDDTs. Right-click and choose Generate DDL. Save the resulting file as some significant name in a location you'll remember.

Edit the file. Remove all references to any sp_bindefault or sp_bindrule statements. ASA does not support defaults or rules in the same way as ASE, so we don't need those.

Go into ISQL for ASA and copy in the contents of the file you just created. Run the query. You should have them all populate in the DB w/o error assuming the stars are lined up correctly.

VIEWS, TRIGGERS, AND STORED PROCEDURES

Open up SQL Advantage and connect to the ASE DB. Issue the appropriate command:
For Triggers:
select "defncopy -Uloginname -Ppassword -SASE_DB out " + name + ".sql "+db_name()+" "+name from sysobjects where type="TR"
For Stored Procedures:
select "defncopy -Uloginname -Ppassword -SASE_DB out " + name + ".sql "+db_name()+" "+name from sysobjects where type="P"
For Views:
select "defncopy -Uloginname -Ppassword -SASE_DB out " + name + ".sql "+db_name()+" "+name from sysobjects where type="V"

Do these separately! (IE: Pick one above. Run its command. Do the below steps to the files that result. Come back here, pick the next data type, run its command, do the below steps. Failure to lather, rinse, and repeat will get you mightily confused.)

These commands will generate a list of command lines. Copy all rows into a .BAT file located in the directory where defncopy is. Run the BAT file. This will generate each of the objects in a separate .sql file. Copy those into an appropriate holding directory.

Go to DOS. CD into the directory that is holding all the .sql files just created in the previous step. Issue the following command:

dir/b *.sql>pop_sp.bat

This will create a batch file named "pop_sp.bat" that contains the bare names of all .sql files in that directory.

Edit this file. Prepend the following on EACH line:

dbisql -c "eng=ASA_DB_ENGINE_NAME;dbn=ASA_DB_NAME;uid=dba;pwd=sql" -nogui -x

Append the following on each line:

1>>stdout.txt 2>>stderr.txt

So you'll end up with lines that look like this:

dbisql -c "eng=ASA_DB_ENGINE_NAME;dbn=ASA_DB_NAME;uid=dba;pwd=sql" -nogui -x user_profiles_p.sql 1>>stdout.txt 2>>stderr.txt

Explanation:
dbisql The command that will import the SQL into the DB
-c Connection parameters, modify them for your ASA details
-nogui Bare-ass text
-x Test this only.
1>>stdout.txt Output all standard output to a file named 'stdout.txt'
2>>stderr.txt Output all errors to a file name 'stderr.txt'

Go to DOS. Run said batch file. Watch the pretty colors.

When done, check stderr.txt. Errors will be in there. You'll get the distinct privilege of fixing them.

STORED PROCEDURE ERRORS

ERROR 1: Stored Procedures sometimes use @@error with a comparison of "! =". It is not legal in ASA to have that space in between, so you have to either rewrite it as "!=" or as "<>".

ERROR 2: The following is a perfectly legal construct in ASE but is not accepted by ASA:
if(condition)
	declare cursor foo
	(manipulate foo)
	deallocate foo
else
	declare cursor foo
	(manipulate foo)
	deallocate foo
end if

(Note to Sybase: This is theoretically unsound (IMHO) and SHOULD BE CORRECTED. Bad Sybase. No bone.)

So, to this end, quite a few stored procs included in the original ASE DB have to be rewritten by renaming the second (and subsequent) occurrences to StoredProcNameN where N is a number starting at 2.

This only works depending on how the script was written, though. Oftentimes, you will see the following:
if(condition)
	DECLARE foo CURSOR FOR (blah)
else
	DECLARE foo CURSOR FOR (bing)
end if
open foo
(use foo)
DEALLOCATE foo
This, too, is illegal in ASA due to (I personally think) a lazy preprocessor that can't be bothered dealing with the scope of the cursor variables. But at any rate, it'll have to be corrected.

You do so by converting the syntax into DECLARE foo CURSOR USING @variable. So something like the following:
DECLARE @SQLstatement varchar(4096)
if(condition)
	SELECT @SQLstatement="blah"
else
	SELECT @SQLstatement="bing"
end if
DECLARE foo CURSOR USING @SQLstatement
open foo
(use foo)
DEALLOCATE foo
Make sure you use the right quotes (single or double) depending on what's inside your cursor. If you have single quotes inside your cursor, you'll have to use doubles and vice versa. If none, use singles, they're easier to type.

So...open a stored procedure, find your cursor in conflict and decide which of the above two types it is and fix it. Run it in ISQL against the DB to test it. If it works, good on ya. If not, fiddle again. Lather, rinse, repeat.

ERROR 3:
Certain SQL statements that use table aliases are invalid in their construction in ASA. These have to be corrected.

SUB-ERROR A:
The first one I had to fix involves ASA disliking aliases of temporary tables. So, I had to create another temp table, copy all rows, perform the update, and then drop the temp table.

SUB-ERROR B:
The second error of this type just involved removing the alias to a table from the statement. It wasn't really necessary (not sure yet why it was there in the first place).

ERROR 4:
The word "message" is a reserved word in ASA and apparently not in ASE. Had to add quotes around it when used as a field name in tables.

ERROR 5:
I had to comment-out all calls to "SET FORCEPLAN" as that is an ASE-only setting.

ERROR 6:
Reserved word "time" was being used in a table creation without being quoted. Added quotes.

ERROR 7:
Had to comment out the "for read only" lines on the INSERT INTO sections of a table as the preprocessor (which I have determined to be a THC-saturated hippie) goes and sees the combination of INSERT and READ ONLY and fruits (even though the READ ONLY is only supposed to apply to the sub-SELECT present in the query).

ERROR 8:
Had to remove "FOR UPDATE OF" statements from a few procedures as they are not supported in ASA. (Looks like a field-level lock.)

ERROR 9:
Reserved word inside a table required putting quotes around the key word date as in "date"

ERROR 10:
ASA does not have multiple databases per server so there is no "tempdb" for temporary tables (that only exist while the server is running) as in ASE. So, I had to rewrite some procedures to use "real" tables as the storage areas for their computations...or rewrite to perform a create a drop.
UN-CONVERTABLES

There are certain (annoying) things that are NOT converted by this process. You will either have to do these manually or come up with other clever ways of handling them.
  • Column defaults
  • User-defined datatypes

CONCLUSION

So, in conclusion, good luck. The process is arduous at best, but hopefully you'll end up with a DB that is very easy to use and maintain and acceptable for your customers. If you have questions/corrections/ideas about these instructions, please do not hesitate to Contact Me and let me know -- I'll never claim these instructions are THE WAY to do it or that they are perfect. But they worked for me.
This site and all content (C)2002-2008 Nathan E. Pralle (www.nathanpralle.com).