Thursday, February 27, 2014

Capturing ALL the output from a Windows command

Question: How do I capture all the output from a Windows command like MOVE? I want the exit code, the standard output, the error message, everything.

You can capture the exit code via %ERRORLEVEL%, and the standard output via MOVE ... >file.txt or >>file.txt, but that doesn't include error messages like "Access denied".

Sometimes, technical questions involving Microsoft products are best asked of StackOverflow Microsoft:

Google: how do i [do some thing] site:microsoft.com
In this case:
Google: how do i capture error messages from the move command site:microsoft.com

Happy, happy, happy...

Redirecting Error Messages from Command Prompt: STDERR/STDOUT talks about the magic 1> and 2> redirection operators, and how you can use 2>&1 to redirect STDERR to STDOUT plus 1> to redirect both to a file.

Here's a move.bat command file to test the technique: it MOVEs two files to a subfolder xxx, where one of the MOVE commands is going to fail because a read-only copy of the file already exists in the subfolder:
ERASE "diag.txt"
ECHO %DATE% %TIME% *** Testing diagnostics written from move.bat to diag.txt...    >>"diag.txt"
MOVE /Y "fff.txt" "xxx\fff.txt"                                                   1>>"diag.txt" 2>&1
ECHO %DATE% %TIME% *** MOVE fff.txt  ERRORLEVEL = %ERRORLEVEL%                     >>"diag.txt" 
MOVE /Y "ggg.txt" "xxx\ggg.txt"                                                   1>>"diag.txt" 2>&1
ECHO %DATE% %TIME% *** MOVE ggg.txt  ERRORLEVEL = %ERRORLEVEL%                     >>"diag.txt" 
ECHO %DATE% %TIME% *** ...all done                                                 >>"diag.txt" 
PAUSE
The Windows console output is now completely incomprehensible but that doesn't matter; this technique is intended for command files that run more-or-less unattended with ECHO OFF to suppress this crap stuff:

The resulting diag.txt file contains everything; Access is denied, 0 file(s) moved and ERRORLEVEL = 1:
Tue 02/25/2014 14:38:28.74 *** Testing diagnostics written from move.bat to diag.txt...    
Access is denied.
        0 file(s) moved.
Tue 02/25/2014 14:38:28.75 *** MOVE fff.txt  ERRORLEVEL = 1                      
        1 file(s) moved.
Tue 02/25/2014 14:38:28.76 *** MOVE ggg.txt  ERRORLEVEL = 0                      
Tue 02/25/2014 14:38:28.76 *** ...all done                                                  



Tuesday, February 25, 2014

Product Suggestion: CREATE EVENT TYPE DDLStatement



From time to time questions arise about auditing DDL statements like CREATE TABLE and GRANT, and the answers are frequently the same:

  • SQL Anywhere doesn't support triggers on DDL statements, and

  • you can use SQL Anywhere's built-in audit feature.
A common knee-jerk response to Answer Number Two is "Go [redact] yourself!"...
Answer Number One, "SQL Anywhere doesn't do DDL triggers" is actually a red herring, as in "a seemingly plausible, though ultimately irrelevant, diversionary tactic". Sure, SQL Server does DDL triggers but "a DDL trigger and the statement that fires it are run within the same transaction" and that's not what we want here.

In fact, if you're serious about auditing DDL statements you want to audit them whether they succeed or fail... like a security camera that watches the loading dock 24-by-7 whether or not Walt and Jesse are successful stealing that barrel of methylamine.

What We Need

What we need is a CREATE EVENT TYPE DDLStatement that works asynchronously on its own connection, and is fired when a DDL statement has finished executing.

With a corresponding EVENT_PARAMETER() call (or two), of course :)

Plus (maybe) a WHERE EVENT_CONDITION() predicate... or two... or three...
CREATE EVENT e TYPE DDLStatement
[   WHERE EVENT_CONDITION ( 'Type' ) = 't'                     -- very nice to have
  | WHERE EVENT_CONDITION ( 'Type' ) IN ( 't' [, 't'] ...      -- nice to have
  | WHERE EVENT_CONDITION ( 'StatementText' ) LIKE 'string' ]  -- would be way cool
HANDLER BEGIN
... 
... EVENT_PARAMETER ( 'StatementText' ) ...                    -- required... pointless without this
...
... EVENT_PARAMETER ( 'SQLCODE' ) ...                          -- very nice to have
...
END;
A restricted list of 't' values might be provided; e.g., 'grant' for all forms of the GRANT statement, and 'create table' for all forms of DDL that create rows in ISYSTAB including views.

The EVENT_PARAMETER and EVENT_CONDITION ( 'StatementText' ) calls would return whatever the server processed, not necessarily what the client sent... this is an audit tool, not a documentation or debugging tool (although it would be invaluable for both purposes :)


Saturday, February 22, 2014

Languages Used, Most Exciting

JavaScript and SQL are in first and second place by a wide margin over Java and the rest in the "Languages Used In The Last Year" category of the 2013 Stack Overflow User Survey Results.

Perhaps it should come as no surprise then, that two of the three "Most Exciting New Technologies" have "JS" in their names :)...




Wednesday, February 19, 2014

Querying HA Status

    Me:  Have you ever started the day 
         with a long list of to-do items, 
         and the first one looks easy,
         but you work all day on it
         and you don't get even that one thing done?

Friend:  No.

    Me:  Me neither :)

Question: How do I show the status of my SQL Anywhere 12 High Availability system?

I want to make sure the arbiter and partner servers are working properly, and I don't have Foxhound installed.

Answer: Use the SQL Anywhere Monitor:
  • Create a "SQL Anywhere Database Resource" that connects to the primary server, then

  • create a "SQL Anywhere Scale-Out Topology Widget" based on that "Resource":


  • The Server Name column shows the real server names defined by the CREATE MIRROR SERVER ... AS PARTNER and CREATE MIRROR SERVER ... AS ARBITER statements.

  • The Type column shows the mirror roles (primary versus mirror) as well as the partner-versus-arbiter breakdown.

  • The State column tells you whether the server is connected or not.

  • Sadly Interestingly, the display doesn't show the alternate server names defined by the CREATE MIRROR SERVER ... AS PRIMARY and CREATE MIRROR SERVER ... AS MIRROR statements.
Here's what the SQL Anywhere Monitor shows when the mirror has crashed:

Here's what failover looks like; sadly interestingly, the rows have changed order to keep the current primary at the top:


Question: The SQL Anywhere Monitor doesn't work for me.

Answer: It looks like the SQL Anywhere Monitor widget only works for declarative High Availability setups. A "declarative" setup uses the new CREATE MIRROR SERVER statement instead of the "old-school" dbsrv12 -xp command line option. The widget is (probably?) looking in the SYSMIRRORSERVER view and that will be empty in an old-school setup.

It is possible to code a generic SQL query that works with any HA setup, be it declarative or old-school:

Here's what it looks like when the secondary (mirror) goes away...

Here's what it looks like after a failover, when the other partner becomes the primary...

With this query, the output is always sorted on the real server name which corresponds to physical computers rather than logical primary and mirror roles.

Also, the status "connected" is shown as "OK" and the primary role is shown in upper case, both of which serve to highlight important distinctions.

No custom application is required to run this query, just an ISQL command file that connects to the primary server; for example:
@ECHO OFF
ECHO.

SETLOCAL ENABLEDELAYEDEXPANSION
SETLOCAL ENABLEEXTENSIONS

"%SQLANY12%\bin64\dbisql.com"^
  -c "SERVER=primary_demo; UID=dba; PWD=sql; HOST=localhost:55401,localhost:55402"^
  READ ENCODING Cp1252 "Query_HA_status.sql" 

IF ERRORLEVEL 1 ( GOTO dbisql_failure ) ELSE ( GOTO end )

:dbisql_failure
ECHO ****************************** 
ECHO *** dbisql.com ERRORLEVEL = %ERRORLEVEL%
ECHO ****************************** 

:end
PAUSE
If you see the ERRORLEVEL message then you know that All Hope Is Lost... if you can't connect to the primary, then HA is completely down.

Here's the Query_HA_status.sql script file... living proof that simple output often requires complex code:
BEGIN

DECLARE LOCAL TEMPORARY TABLE ha_status (
   real_server_name      VARCHAR ( 128 ) NOT NULL PRIMARY KEY,
   role                  VARCHAR ( 7 ) NOT NULL,
   alternate_server_name VARCHAR ( 128 ) NOT NULL,
   state                 VARCHAR ( 100 ) NOT NULL )
   NOT TRANSACTIONAL;

DECLARE @MAX_LENGTH_real_server_name      INTEGER;
DECLARE @MAX_LENGTH_role                  INTEGER;
DECLARE @MAX_LENGTH_alternate_server_name INTEGER;
DECLARE @MAX_LENGTH_state                 INTEGER;
DECLARE @MAX_line_length                  INTEGER;

INSERT ha_status
SELECT PROPERTY ( 'ServerName' )                    AS real_server_name,
       'PRIMARY'                                    AS role,
       DB_PROPERTY ( 'AlternateServerName' )        AS alternate_server_name,
       CASE
          WHEN sa_mirror_server_status.state  = 'connected'
           AND DB_PROPERTY ( 'ReadOnly' )     = 'Off'
             THEN 'OK'           -- always expected 
          WHEN sa_mirror_server_status.state = 'disconnected'
             THEN 'disconnected' -- never expected
          ELSE STRING (          -- never expected  
             sa_mirror_server_status.state,
             ', ReadOnly=',
            DB_PROPERTY ( 'ReadOnly' ) )
       END CASE                                     AS state
  FROM sa_mirror_server_status() 
 WHERE sa_mirror_server_status.server_name = PROPERTY ( 'ServerName' )
   AND DB_PROPERTY ( 'MirrorRole' ) = 'primary' -- assertion
UNION ALL
SELECT sa_mirror_server_status.server_name          AS real_server_name, 
       'mirror',
       DB_PROPERTY ( 'AlternateMirrorServerName' )  AS alternate_server_name,
       CASE
          WHEN sa_mirror_server_status.state  = 'connected'
           AND DB_PROPERTY ( 'PartnerState' ) = 'connected'
           AND DB_PROPERTY ( 'MirrorState' )  = 'synchronized'
             THEN 'OK'
          WHEN sa_mirror_server_status.state = 'disconnected'
             THEN 'disconnected'
          ELSE STRING (  
             sa_mirror_server_status.state,
             ', ',
             DB_PROPERTY ( 'PartnerState' ),
             ', ',
             DB_PROPERTY ( 'MirrorState' ) )
       END CASE                                     AS state
  FROM sa_mirror_server_status() 
 WHERE sa_mirror_server_status.server_name <> PROPERTY ( 'ServerName' )
   AND DB_PROPERTY ( 'MirrorRole' ) = 'primary' -- assertion
UNION ALL
SELECT COALESCE ( SYSMIRRORSERVER.server_name, '' ) AS real_server_name,
       'arbiter'                                    AS role,
       ''                                           AS alternate_server_name,
       IF DB_PROPERTY ( 'ArbiterState' ) = 'connected'
          THEN 'OK'
          ELSE DB_PROPERTY ( 'ArbiterState' )
       END IF                                       AS state
  FROM DUMMY 
          LEFT OUTER JOIN 
             ( SELECT * 
                 FROM SYSMIRRORSERVER
                WHERE SYSMIRRORSERVER.server_type = 'arbiter' ) AS SYSMIRRORSERVER
          ON 1 = 1
   AND DB_PROPERTY ( 'MirrorRole' ) = 'primary' -- assertion
UNION ALL
SELECT 'This query can only be run against the current primary server.', '', '', ''
  FROM DUMMY
 WHERE DB_PROPERTY ( 'MirrorRole' ) <> 'primary';

SELECT MAX ( LENGTH ( ha_status.real_server_name ) ) + 1,
       MAX ( LENGTH ( ha_status.role ) ) + 1,
       MAX ( LENGTH ( ha_status.alternate_server_name ) ) + 1,
       MAX ( LENGTH ( ha_status.state ) ) 
  INTO @MAX_LENGTH_real_server_name,
       @MAX_LENGTH_role,
       @MAX_LENGTH_alternate_server_name,
       @MAX_LENGTH_state
  FROM ha_status;

SET @MAX_line_length 
  = @MAX_LENGTH_real_server_name 
  + @MAX_LENGTH_role 
  + @MAX_LENGTH_alternate_server_name 
  + @MAX_LENGTH_state;

SELECT LEFT ( STRING ( 
                 LEFT ( STRING ( ha_status.real_server_name, REPEAT ( ' ', 129 ) ),       
                        @MAX_LENGTH_real_server_name ), 
                 LEFT ( STRING ( ha_status.role, REPEAT ( ' ', 8   ) ),       
                        @MAX_LENGTH_role ),
                 LEFT ( STRING ( ha_status.alternate_server_name, REPEAT ( ' ', 129 ) ),       
                        @MAX_LENGTH_alternate_server_name ),
                 ha_status.state ), 
              @MAX_line_length ) AS "High Availability Status"
  FROM ha_status
 ORDER BY ha_status.real_server_name;

END;
The table defined on line 3 is filled with three rows for the arbiter, primary and mirror. For an old-school setup, the real_server_name gets the dbsrv12 -n name (except for the arbiter server where this column is empty), the role is set to 'arbiter', 'PRIMARY' or 'mirror', the alternate_server_name is filled with the dbsrv12 -sn or -sm primary or mirror server names, and the state contains 'OK' or something less friendly.

For a declarative HA setup, the table is the same except (a) the real_server_name is filled in and (b) some of the values come from CREATE MIRROR SERVER statements rather than the dbsrv12 command line.

The INSERT on line 16 is a UNION of four SELECT statements. The first fills the 'PRIMARY' row, the second SELECT fills the 'mirror' row, the third fills the 'arbiter' row and the fourth SELECT returns an error message in case your ISQL connection string isn't properly set up to connect to the primary.

The INSERT relies on information from three sources:
The arbiter is a special case. There's no PROPERTY() or DB_PROPERTY() call you can make on the primary to get the arbiter's real server name, and you can't connect to the arbiter in any meaningful way since it doesn't have a database. The only place the real server name for the arbiter is available is SYSMIRRORSERVER, which accounts for the funky FROM DUMMY LEFT OUTER JOIN starting on line 63; i.e., it causes the COALESCE on line 56 to return '' for an old-school setup and a non-empty SYSMIRRORSERVER.server_name value for a declarative setup.

The SELECT and SET statements on lines 75 through 89 calculate various display widths so the output can be crammed to fit in an ordinary Windows "DOS box" display window.

The final SELECT on line 91 returns the fixed-column-format output that is shown in the earlier images. The ORDER BY on line 101 guarantees that you can always see at-a-glance when the PRIMARY moves from one partner to another and so on... the real server names remain in the same order, whereas the 'PRIMARY' and 'mirror' roles move up and down.

For The Record

Here's what Foxhound shows for an HA setup (old-school or declarative) that's running OK:



Here's what Foxhound shows when the mirror crashes (the primary keeps running):



Here's what failover looks like (the primary crashed so the former mirror became the primary):





Friend:  What makes you think anyone's going to buy Foxhound
         if you keep showing them how to do-it-yourself?

    Me:  It's not do-it-yourself if someone else does it for you.

Friend:  [fretful sighing sound] 
         You know what I mean...

    Me:  Yeah... but I don't know the answer :)


Friday, February 14, 2014

Outer Join Not Behaving As Expected

This article was inspired by the "left outer join not working as expected" conversation on the SQL Anywhere forum...

The Question

How do I show the total bonus amount paid to each employee hired in 2013 or later?

The Data

CREATE TABLE employee (
   employee_id    INTEGER NOT NULL PRIMARY KEY,
   hire_date      DATE NOT NULL );

CREATE TABLE bonus ( 
   employee_id    INTEGER NOT NULL REFERENCES employee ( employee_id ),    
   bonus_date     DATE NOT NULL,
   bonus_amount   DECIMAL ( 9, 2 ) NOT NULL,
   PRIMARY KEY ( employee_id, bonus_date ) );

INSERT employee VALUES ( 1, '2011-02-01' );
INSERT employee VALUES ( 2, '2012-02-01' );
INSERT employee VALUES ( 3, '2013-02-01' );
INSERT employee VALUES ( 4, '2014-02-01' );
INSERT bonus VALUES ( 1, '2011-12-01', 100.00 );
INSERT bonus VALUES ( 1, '2012-12-01', 100.00 );
INSERT bonus VALUES ( 1, '2013-12-01', 100.00 );
INSERT bonus VALUES ( 2, '2012-12-01', 100.00 );
INSERT bonus VALUES ( 2, '2013-12-01', 100.00 );
INSERT bonus VALUES ( 3, '2013-12-01', 100.00 );
COMMIT;

The Expected Output

employee_id hire_date          total_bonus 
----------- ---------- ------------------- 
          2 2012-02-01              200.00 
          3 2013-02-01              100.00 
          4 2014-02-01                0.00 

The Answer

  • Employee 4 doesn't have any bonus rows at all so a LEFT OUTER JOIN is required,

  • plus a SUM and GROUP BY to calculate total_bonus

  • and a COALESCE to turn Employee 4's NULL amount into zero

  • plus the predicate employee.hire_date >= '2012-01-01' to eliminate Employee 1:
SELECT employee.employee_id,
       employee.hire_date,
       COALESCE ( SUM ( bonus.bonus_amount ), 0.00 ) AS total_bonus
  FROM employee LEFT OUTER JOIN bonus
       ON employee.employee_id = bonus.employee_id
       AND employee.hire_date >= '2012-01-01'
 GROUP BY employee.employee_id,
       employee.hire_date
 ORDER BY employee.hire_date;

Thank you for playing, next contestant please!

That might be the expected output, but here's the actual output from "The Answer" above:
employee_id hire_date          total_bonus 
----------- ---------- ------------------- 
          1 2011-02-01                0.00 
          2 2012-02-01              200.00 
          3 2013-02-01              100.00 
          4 2014-02-01                0.00 
Employee 1 is included in the output in spite of the predicate employee.hire_date >= '2012-01-01'

Why is that?

There's a clue in the Help:
A left or right outer join of two tables preserves all the rows in one table, and supplies nulls for the other table when it does not meet the join condition.
Employee 1 does not meet the join condition (because employee.hire_date >= '2012-01-01' is FALSE) so the row is preserved and NULL is supplied for the corresponding bonus.bonus_amount, with the end result being a zero-amount row for Employee 1 is included in the output.

That IS the expected result for the query shown above... not the expected result for ordinary folk (like me, and perhaps like you as well), but the expected result for whoever invented outer joins.

The Help actually contains what appears to be contrary advice:
Outer joins and join conditions

A common mistake with outer joins is the placement of the join condition. If you place restrictions on the null-supplying table in a WHERE clause, the join is usually equivalent to an inner join.

The reason for this is that most search conditions cannot evaluate to TRUE when any of their inputs are NULL. The WHERE clause restriction on the null-supplying table compares values to NULL, resulting in the elimination of the row from the result set. The rows in the preserved table are not preserved and so the join is an inner join.

The exception to this is comparisons that can evaluate to true when any of their inputs are NULL. These include IS NULL, IS UNKNOWN, IS FALSE, IS NOT TRUE, and expressions involving ISNULL or COALESCE.

For example, the following statement computes a left outer join.
SELECT *
FROM Customers KEY LEFT OUTER JOIN SalesOrders
   ON SalesOrders.OrderDate < '2000-01-03';
In contrast, the following statement creates an inner join.
SELECT Surname, OrderDate
FROM Customers KEY LEFT OUTER JOIN SalesOrders
   WHERE SalesOrders.OrderDate < '2000-01-03';
The first of these two statements can be thought of as follows: First, left-outer join the Customers table to the SalesOrders table. The result set includes every row in the Customers table. For those customers who have no orders before January 3 2000, fill the sales order fields with nulls.

In the second statement, first left-outer join Customers and SalesOrders. The result set includes every row in the Customers table. For those customers who have no orders, fill the sales order fields with nulls. Next, apply the WHERE condition by selecting only those rows in which the customer has placed an order since January 3 2000. For those customers who have not placed orders, these values are NULL. Comparing any value to NULL evaluates to UNKNOWN. So, these rows are eliminated and the statement reduces to an inner join.

Now that is excellent advice, but it applies to predicates involving the table on the nullable side of an outer join; e.g., the table on the right side of a LEFT OUTER JOIN. In this case the "clue" applies: employee.hire_date >= '2012-01-01' involves the preserved side of the outer join (i.e., the table on the left side of the LEFT OUTER JOIN) so the predicate is effectively ignored for Employee 1...

... and that's what doesn't make sense to ordinary folk.

One solution is to move the predicate into the WHERE clause. Even though that seems to violate the "common mistake" exhortation above, it really doesn't... and it works by excluding Employee 1:
SELECT employee.employee_id,
       employee.hire_date,
       COALESCE ( SUM ( bonus.bonus_amount ), 0.00 ) AS total_bonus
  FROM employee LEFT OUTER JOIN bonus
       ON employee.employee_id = bonus.employee_id
 WHERE employee.hire_date >= '2012-01-01'
 GROUP BY employee.employee_id,
       employee.hire_date
 ORDER BY employee.hire_date;

employee_id hire_date          total_bonus 
----------- ---------- ------------------- 
          2 2012-02-01              200.00 
          3 2013-02-01              100.00 
          4 2014-02-01                0.00 
There's a better way, however, one that doesn't require you to open the textbook every time you want to code an outer join:

Use derived tables to divide and conquer

A derived table is like a local view. Instead of coding a separate CREATE VIEW or even using a WITH clause, you can code a SELECT right inside the FROM clause. And in this case, that inner SELECT can apply the predicate employee.hire_date >= '2012-01-01' before the LEFT OUTER JOIN is even executed:
SELECT employee.employee_id,
       employee.hire_date,
       COALESCE ( SUM ( bonus.bonus_amount ), 0.00 ) AS total_bonus
  FROM ( SELECT *
           FROM employee 
          WHERE employee.hire_date >= '2012-01-01' 
       ) AS employee
       LEFT OUTER JOIN bonus
       ON employee.employee_id = bonus.employee_id
 GROUP BY employee.employee_id,
       employee.hire_date
 ORDER BY employee.hire_date;

employee_id hire_date          total_bonus 
----------- ---------- ------------------- 
          2 2012-02-01              200.00 
          3 2013-02-01              100.00 
          4 2014-02-01                0.00 
Two tricks make derived tables easier to work with: First, use SELECT * instead of worrying about a column list, and second, use the original table name as the alias name: AS employee. In this example, the only code that changed was this:
  • The table name "employee" in the FROM clause was changed to "( SELECT ... ) AS employee"

  • and the WHERE clause was eliminated.
That might not seem like much for a trivial query like this, but it helps a lot for giant complex queries... including ones that are having INNER JOINS retrofitted with LEFT OUTER JOINS.

Wednesday, February 12, 2014

Techwave 2014 News

from: ISUG-TECH Conference conference@isug.com
date: Tue, Feb 11, 2014 at 10:25 AM
subject: Last Day for 2014 ISUG-TECH Conference Ultra Early Bird Special is February 14th


This coming Friday, February 14th, is the final day to take advantage of our ultra early bird special for the ISUG-TECH 2014 Conference to be held in Atlanta, GA, on April 14-17. After that day you will have lost the chance to save more than $400 on your hotel accommodations for this event! That's right: this is the last week to get three nights of hotel included in your registration fee. Once Saturday rolls around the chance will be lost forever!

And we have some additional exciting news to share as well. Our conference will be the exclusive first look at the latest release of SAP ASE - version 16! This is a can't miss event, but don't take our word for it. Listen to what our good friend Peter Thawley had to say:

I can't tell you all how excited all of us old-time Sybase folks are for the ISUG-TECH conference coming up in mid-April. For those readers on the fence here, let me tell you unequivocally that this will not be like one of the more recent events you may have attended. This event will be highly technical with little to no marketing.

I and my entire extended Sybase team, people like Jeff Tallman, Rob Verschoor, Chris Brown, Chris Baker, Richard Soundy, Mark Mumy, Javier Cuerva, Paul Vero, and countless other hard core geeks and propeller heads will make sure your heads hurt by the end of the conference.

Join us and celebrate the coming out party for SAP ASE 16 at what will undoubtably be the best Sybase event since the days of old. Oh yeah, and don't be stupid by missing the early registration perks ... like a free hotel room ... are you kidding me!

We're really excited to be the venue for this launch, and we hope you are too! Virtually all of the conference content is now online, although we are still adding the SAP ASE 16-specific sessions as they are finalized by Peter and his team.

But it's not just SAP ASE at the conference. We have six other product tracks during the conference: SAP HANA, SAP IQ, SAP PowerDesigner, SAP PowerBuilder, SAP Replication Server and SAP SQL Anywhere. There will also be a general SAP Development track that will cover developing applications for a variety of SAP Products, such as SAP AppBuilder, SAP Mobile Platform, SAP River, and more.

Altogether, we will be presenting more than 150 hours of in-conference sessions, and more than 60 hours of optional workshops before and after the main event. We have some phenomenal speakers who will be sharing their experiences, best practices and advice with our attendees. Some of the biggest names in the SAP database and developer ecosystem will be in Atlanta so be sure not to miss out!

We're pleased to announce that SAP is our Diamond Sponsor for this conference. The support and commitment we've received from SAP is outstanding and we are all excited to see the company's passion in making this event a big success. But we need you to join us to make that happen. General registration for the conference is $1195, but our Gold members will receive a special discount of $300 off that fee. (Learn more about our Gold membership here.)

So don't delay; take a moment now to explore the conference sessions and workshops, and then start your registration!

Best Regards,
The ISUG-TECH Conference Team

The ISUG-TECH Conference 2014


Finally the call has been answered for a hands-on technical conference focused on former Sybase and now SAP technologies across the whole stack from ASE to PowerBuilder and onward to HANA and beyond.

by Adrian Bridgwater, ISUG-Tech Journal

The ISUG-TECH Conference 2014 will be staged at the Hilton Downtown Hotel in Atlanta, Georgia on the 14-17 April. That sentence took some time to come around and felt pretty darn amazing to write as well. It was 2011 when we last saw the conference we knew as Sybase TechWave come about -- and at that time it was already running as a (smaller) sister event to the wider SAP TechEd event.

Nurture, Champion and Propagate


After that point in time, from 2012 until now, we have been left with what many considered to be a lack of clear focus from SAP as to what it’s long long long (yes, x3 longs) term plans were with the element of the former Sybase stack and whether it would ultimately decide to nurture, champion and propagate for further development. Things became clearer in the latter part of 2013 and the ISUG-TECH team assembled in Las Vegas for TechEd 2013 to share as best we could the message set for learning and greater personal skill value which we have long worked towards. It was during a few contemplative moments at that show that the germ of an idea was fi nally given time to blossom. What if we could stage a conference for developers and data professionals that took the “technical” (OK so all SAP is technical, but you know what we are talking about) aspects of the SAP family as it now sits and create a handson event with a real opportunity to get to grips with tools and the latest release of all the products we know from PowerBuilder to ASE.

So the planning stages were conducted and sparks flew, the shape of the conference quickly came together and now we know that the ISUG-TECH Annual Conference 2014 will feature more than 150 hours of conference sessions, and more than 60 hours of optional workshops.

“We have gathered all the right experts together to share their experiences, best practices and offer sound advice for getting the most out of SAP products and solutions. I’m proud to see that this event has come together so tightly as it shows the level of commitment our members, speakers, editorial writers and evangelists have for our mission. It also shows how SAP is committed to backing us and ISUG-TECH is delighted to say that SAP is acting as Diamond Sponsor for the event itself,” said Mike Harrold, executive director at ISUG-TECH.

Seven Product Tracks


The seven product tracks in the event include: SAP ASE, SAP HANA, SAP IQ, SAP PowerDesigner, SAP PowerBuilder, SAP Replications-Server, SAP SQL Anywhere. There will also be a general SAP development track on application development for a variety of SAP products.

As a special note of interest, this conference feature the exclusive launch of SAP ASE 16. Yes we do mean Adaptive Server Enterprise and we do mean release iteration number 16 in all its finery and glory. This is a chance for attendees to get first hand insights on product direction and learn directly from the SAP experts where and how ASE can be used in real world implementations.

So as the workshops list fills up, now is your time to register and start looking at what sessions you want to attend. If we have to stop and tell you how good these presentations are, we have SPEAKERS signing up for other people's workshops! If that’s not dogfooding on your own technology and digging deep in a truly organic kind of way, then to be quite honest we don’t know what is.

The ‘Drawcard’ Speaker List


The list of speakers for this event is impressive and unmatched. It would be remiss of us not to quickly name everybody from our speaker list so here are the names with each speaker’s place of work listed right after their name: Phillip Adams, FireSteed Consulting; Bruce Armstrong, IDS; Chris Baker, SAP; Peter Barnett, IBM; Mark Bemis, Fortress Medical Systems; Chris Brown, SAP; Murat Cetinkaya, ING Bank, and VinodChandran, SAP.

Also we will hear from Matt Creason, SAP; Lloyd Dickman, Bay Storage Technology; Joe Woodhouse, Prima Donna Consulting; Marcel Schot, Martijn Evers, DataMasters; Dave Fish, SAP; Jeff Garbus, Soaring Eagle Consulting; Janis Griffin, Confio Software; John Hnat, PowerSeller Solutions; Brian Le Suer, Zeenyz Software; George McGeachie, Metadata Matters; Tom McWilliams, Bay Storage Technology; Armeen Mazda, Appeon; Ricardo Murcia, SAP; Michael Novak, SAP; Roberto Peña, Unibanco; Ronnie Po, Morpheon; Jose Ramos, SAP; ApurbaSaha, Columbia University; Cory Sane, Wells Fargo, Filiberto Sosa, Sizes and Colors of Mexico; Richard Soundy, SAP; Dharmapalan Sreekumar, ANZ Bank; Jeff Tallman, SAP; Paul Vero, SAP and Rob Verschoor, SAP. Note: All speakers subject to change.

What Makes this Event so Special?


What really makes this event special this year is probably the gap in between 2011 and now that we mentioned at the start of this piece. This has been a period of consolidation and focus for SAP as the firm has spent considerable time in the public spotlight reshaping its cloud proposition and building the HANA brand to be what is it now. Okay so I suppose we couldn’t through all of this without mentioning HANA and it’s good to mention it here as we look to the way our presenters will reference the wider SAP stack in all of their presentations. This conference will be hands on, this conference will be enterprise but informal, this conference will be hugely appreciated by everybody who attends due to the emotional and technical blood, sweat and tears that have gone into it -- or to put it another way, this will be a great conference. We can’t wait to see you in Atlanta!

For golf fans, the Masters Golf Tournament in Augusta is the week prior to the conference, so you may consider spending the weekend before the conference relaxing and enjoying the golf before joining us for the main event!

How do you get to Atlanta with ISUG-TECH?


General registration for the conference is US$1195, but ISUG-TECH Gold members will receive a special discount of $300. You can register now at: www.isug.com/conference.

Monday, February 10, 2014

Characteristic Errors, Revision 5

Back in August this list had 42 entries, now it has 48...


A characteristic error is an error that is so easy to make that it appears you are being actively encouraged to make it by the very nature of the computer program you are using.

For example, sending an email without the attachment is a characteristic error of all email programs.

...except Gmail. Gmail warns you about missing attachments... Gmail is magic!

Here are the new entries...
  1. SQL: Forgetting the comma between two columns in SELECT list, thus turning the second column name into a profoundly misleading alias name for the first column.

  2. SQL Anywhere: Coding SET OPTION instead of SET TEMPORARY OPTION, then wondering where the commit came from.

  3. SQL: Not bothering to check that you have the same number of columns and arguments in an INSERT statement and then wondering why you have an error (thank you, Justin Willey).

  4. SQL: Forgetting to omit your autoincrement primary key from your INSERT column name list (thank you, Justin Willey).

  5. Foxhound: Changing the "Default" Monitor option settings, then expecting them to apply to an existing target database.

  6. Foxhound: Creating two separate Foxhound Monitor sessions for the same actual target database, perhaps one using a DSN and the other a DSN-less connection string.


Here's the full list...
  1. SQL: Seeing too little data, or no data at all, because a predicate in the WHERE clause effectively turned your OUTER JOIN into an INNER JOIN.

  2. SQL: Seeing too much data because a missing predicate effectively turned your INNER JOIN into a CROSS JOIN.

  3. SQL: Getting the wrong COUNT() or SUM() because you forgot to code WHERE ... IS NOT NULL, or you *did* code it when you shouldn't have.

  4. SQL: Getting the wrong answer because you forgot that, in general, NULL values [cough] suck.

  5. SQL Anywhere: Not seeing MESSAGE output because you forgot to run SET TEMPORARY OPTION DEBUG_MESSAGES = 'ON';

  6. SQL Anywhere: Not seeing any data because you forgot ON COMMIT PRESERVE ROWS or NOT TRANSACTIONAL.

  7. SQL Anywhere: Coding ENDIF where END IF was required, or vice versa (before Version 11).

  8. SQL Anywhere: Connecting to the wrong server because you forgot DOBROAD=NONE (before Version 12).

  9. SQL Anywhere: Forgetting the asterisk in SELECT TOP 10 FROM ...

  10. SQL Anywhere: Coding IF NOT VAREXISTS ( 'x' ) THEN ... instead of IF VAREXISTS ( 'x' ) = 0 THEN ...

  11. SQL Anywhere: Coding the wrong magic numbers 1, 2, 3, ... in the get_value() and set_value() calls in an EXTERNAL C DLL function.

  12. SQL Anywhere: Getting proxy table ODBC errors because the engine's running as a service and you've set up a User DSN instead of System DSN.

  13. SQL Anywhere: Getting file-related errors because the file specifications are relative to the server rather than the client.

  14. SQL Anywhere: Getting file-related errors because the engine's running as a service without the necessary permissions.

  15. SQL Anywhere: Coding CREATE TRIGGER IF NOT EXISTS instead of CREATE OR REPLACE TRIGGER, or vice versa for CREATE TABLE (in 11.0.1 or later).

  16. SQL Anywhere: Getting integer arithmetic when you wanted fractional parts because you forgot to CAST.

  17. Stored procedure debugger: Setting it to watch a specific user id other than the one you're using to test your code.

  18. Sybase Central: Setting it to display objects for owner names other than the one you're interested in.

  19. Copy and paste: Forgetting to edit after pasting; e.g., Copy and paste SET @continue = 'Y' into the body of a WHILE loop and then forgetting to change it to 'N'.

  20. MobiLink: Forgetting to call ml_add_column for any of the columns you're trying to synchronize, thus guaranteeing yourself a "Sassen Frassen Fricken Fracken!" moment when you run the first test.

  21. MobiLink: Forgetting to call ml_add_[various] with the NULL parameter to delete old ml_[whatever] rows, thus ending up with thousands of orphan system table rows in the consolidated database.

  22. OLAP Windowing: Coding the wrong combination of ASC and DESC in an inner OVER ORDER BY clause and the outer SELECT ORDER BY: different when they should be the same, the same when they should be different, or some other variation of "wrong combination"...
    SELECT older_sample_set.sample_set_number
                  INTO @20_older_sample_set_number
                  FROM ( SELECT TOP 20
                                ROW_NUMBER() OVER ( ORDER BY rroad_sample_set.sample_set_number ASC ) AS scrolling_row_number,
                                rroad_sample_set.sample_set_number                                    AS sample_set_number
                           FROM rroad_sample_set
                          WHERE rroad_sample_set.sampling_id       = @sampling_id
                            AND rroad_sample_set.sample_set_number < @sample_set_number
                          ORDER BY rroad_sample_set.sample_set_number DESC ) AS older_sample_set
                 WHERE older_sample_set.scrolling_row_number = 20;

  23. MobiLink: Forgetting to call ml_add_column() when trying to use named parameters instead of "?" in versions 10 and 11 MobiLink scripts, resulting in a "What the ... ? Sassen Frassen Fricken Fracken!" moment during the first test (thank you, Jeff Albion).

  24. SQL: Omitting a PRIMARY KEY column from the WHERE clause, thus turning a singleton SELECT (or DELETE!) into something rather more enthusiastic than expected (thank you, Ron Hiner).

  25. HTTP web services: Leaving an & in the code when a ? is required, and vice versa, when editing service URLs; e.g., 'HTTP://localhost:12345/web_service&service_parm2=!parm2'

  26. SQL Anywhere: Forgetting that not all functions look like functions: SELECT CAST ( CURRENT TIMESTAMP, VARCHAR )

  27. Batch file: Trailing spaces on SET commands; e.g., SELECT CAST ( xp_getenv ( 'DEBUG_MESSAGES' ) AS VARCHAR ) returns 'OFF ' instead of 'OFF' after SET DEBUG_MESSAGES=OFF

  28. Forum: Clicking Reply on the main Question or Answer entry instead of the comment you wanted.

  29. SQL Anywhere: Forgetting to run dblog to tell the database file where the log is now, after moving the database and log files to a different folder (thank you, Justin Willey).

  30. SQL Anywhere: Having to look up WAIT in the Help ... every ... single ... time, to be reminded that's it's WAITFOR, not WAIT.

  31. SQL: Forgetting to check the SELECT against the GROUP BY, resulting in "Function or column reference to ... must also appear in a GROUP BY" (thank you, Glenn Paulley).

  32. SQL: Coding too much in the GROUP BY (like, say, the primary key) so every group contains but a single row (thank you, Glenn Paulley).

  33. Design: Forgetting to accomodate or prevent loops in a tree structure, resulting in a tree traversal process that pegs the CPU at 100%... forever (thank you, Ove B).

  34. MobiLink: Unwittingly using a variety of user ids when running sync*.sql, updating MobiLink scripts and running the MobiLink server, resulting in inexplicable inconsistencies.

  35. MobiLink: Accidentally creating multiple script versions and then getting them crossed up when updating MobiLink scripts and running the MobiLink client.

  36. SQL Anywhere: Forgetting to run the 32-bit version of SQL Anywhere when working with Excel proxy tables.

  37. ODBC Administrator: Running the 64-bit version (huh?) of odbcad32.exe (say what?) when you need 32-bit version at C:\WINDOWS\SysWOW64\odbcad32.exe (oh, fer #*@&!!!)

  38. ODBC Administrator: Forgetting to click OK ... twice ... to actually save your new ODBC DSN after celebrating your success with Test Connection.

  39. ODBC Administrator: Setting up an ODBC DSN on the wrong computer: "It goes with the client!" ... but sometimes it's not obvious where the client is actually located.

  40. Security: Forgetting which Windows user id you're using on which system, then spending too much time with Windows menus, firewall software and Google searches before the "Doh!" moment.

  41. SQL: Getting an exception that is not only completely inexplicable, but absolutely impossible for the statement that raised it... until you think to look inside the triggers.

  42. SQL Anywhere: Getting an exception because a FOR loop variable has a scope conflict with a column name, or worse, NOT getting an exception, just a wrong result.

  43. SQL: Forgetting the comma between two columns in SELECT list, thus turning the second column name into a profoundly misleading alias name for the first column.

  44. SQL Anywhere: Coding SET OPTION instead of SET TEMPORARY OPTION, then wondering where the commit came from.

  45. SQL: Not bothering to check that you have the same number of columns and arguments in an INSERT statement and then wondering why you have an error (thank you, Justin Willey).

  46. SQL: Forgetting to omit your autoincrement primary key from your INSERT column name list (thank you, Justin Willey).

  47. Foxhound: Changing the "Default" Monitor option settings, then expecting them to apply to an existing target database.

  48. Foxhound: Creating two separate Foxhound Monitor sessions for the same actual target database, perhaps one using a DSN and the other a DSN-less connection string.


Wednesday, February 5, 2014

Latest SQL Anywhere Updates: 16.0.0.1785 for Mac OS

Current builds for the active platforms...

HP-UX     16.0.0.1691 (EBF 22263 SP6)        11 Dec 2013
 Itanium  12.0.1.3994 (EBF 22137 SP66)       02 Dec 2013
          11.0.1.2958 (EBF 21793 SP96)       08 Apr 2013     End of Life 31 May 2014

IBM AIX   16.0.0.1691 (EBF 22262 SP6)        11 Dec 2013
          12.0.1.3994 (EBF 22136 SP66)       02 Dec 2013
          11.0.1.2958 (EBF 21792 SP96)       08 Apr 2013     End of Life 31 May 2014

Linux     16.0.0.1691 (EBF 21981 SP6)        31 Oct 2013
          On Demand 1.0.4613 (SP3)           13 Sep 2013
          12.0.1.3994 (EBF 22111 SP66)       27 Nov 2013
          12.0.1 Chinese,                    16 Apr 2013
                 Japanese Docs (Eclipse)     16 Apr 2013
          11.0.1.3027 (EBF 21786 SP98)       13 Sep 2013     End of Life 31 May 2014

Mac OS    16.0.0.1785 (EBF 22412 SP8)    *** 04 Feb 2014 ***
          12.0.1.4006 (EBF 22318 SP67)       24 Dec 2013
          11.0.1.2449 Update                 29 Jun 2010     End of Life 31 May 2014

Solaris   16.0.0.1691 (EBF 22267 SP6)        11 Dec 2013
 SPARC    12.0.1.3994 (EBF 22138 SP66)       03 Dec 2013
          11.0.1.2958 (EBF 21794 SP96)       08 Apr 2013     End of Life 31 May 2014

Solaris   16.0.0.1691 (EBF 22265 SP6)        11 Dec 2013
 x64      12.0.1.3994 (EBF 22139 SP66)       02 Dec 2013
          11.0.1.2958 (EBF 21750 SP96)       08 Apr 2013     End of Life 31 May 2014

Windows   16.0.0.1761 (EBF 22392 SP7)        22 Jan 2014
          On Demand 1.0.4613 (SP3)           13 Sep 2013
          12.0.1.3994 (EBF 22109 SP66)       27 Nov 2013
          12.0.1 French,                     25 Sep 2012
                 English,                    25 Sep 2012
                 German,                     25 Sep 2012
                 Chinese,                    28 Mar 2013
                 Japanese Docs (HTML/PDF)    28 Mar 2013
          11.0.1.3069 (EBF 22299 SP99)       19 Dec 2013     End of Life 31 May 2014 

Other Stuff...

 Older Updates

 Free support! Q&A forum
   ...or, call Tech Support

 SQL Anywhere...
   ...Sybase home page 
   ...SAP home page 
   ...SAP Developer Center 

 Buy SQL Anywhere 

 Developer Edition... 
   [16.0] [12.0.1(?)] [11.0.1]

 Download the Educational Edition 

 Supported Platforms...
   SQL Anywhere 
   Linux 
   OnDemand

 ODBC Drivers for MobiLink

The asterisks "***" show which items have appeared on the Sybase website since the previous version of this page.
  • Only the latest fully-supported versions of SQL Anywhere (11.0.1, 12.0.1, 16.0 and On Demand) are shown here.

  • The "EBF 21788 SP60" numbers are the new SAP-specific codes associated with the build numbers "12.0.1.3894".

  • Just because an older version or different platform isn't "fully supported" any more doesn't mean you can't download files (or ask questions, or get help), it just means there won't be any more new Updates released.

Monday, February 3, 2014

New Features In SQL Anywhere 16.0.0.1761

An earlier article listed the new features added to SQL Anywhere 16 after its introduction (build 1324) up to and including build 16.0.0.1691.

This article lists the new features added after that, up to and including 16.0.0.1761.

1745 MobiLink Plugin support for -zup switch - for more information see 1723 MobiLink Server enhancements to LDAP authentication
1733 MobiLink Server support for Oracle 12.1
1723 MobiLink Server enhancements to LDAP authentication
1733 Oracle ODBC driver support for Oracle 12.1
1760 UltraLite support for Xcode 5 and iOS 7
1716 UltraLite support for Windows 8 and Windows Phone 8

  MobiLink - Java Plugin for Sybase Central

    ================(Build #1745  - Engineering Case #751936)================

 The MobiLink Plugin now supports:
 
 The new MobiLink Server –zup switch.  This switch can be accessed from the 
 Advanced tab of the property sheet for a MobiLink Server Command Line. An 
 error is reported if –zup is set and –zu is set to false, as this is not 
 allowed.
 
 For user authentication policies we now support calling the standard MobiLink 
 authentication scripts: Never, Always or only when an LDAP server could not 
 be found.  This change affects the LDAP Servers page of the New User Authentication 
 Policy wizard. It also affects the property sheet for user authentication 
 policies. The setting is also shown in the right hand pane when User Authentication 
 Policies are shown.
 MobiLink - Synchronization Server

    ================(Build #1733  - Engineering Case #751199)================

 The MobiLink server now supports consolidated databases running on an Oracle 
 12.1 server.  In order to use any of these Oracle 12.1 new features: 32K-byte 
 varchar2, nvarchar2, raw data types, and implicit result sets, the build 
 numbers of the SQLA Oracle ODBC driver must be greater than or equal to 1733 
 and the Oracle OCI library must be installed from the Oracle 12.1 installation 
 image.
    ================(Build #1723  - Engineering Case #750296)================

 Three new features/modifications have been introduced in the MobiLink server:
 
 a) The setting for the ldap_failover_to_std property for a user authentication 
 policy is extended to be 0, 1, or 2 [it originally accepts only 0 (FALSE) 
 or 1 (TRUE)).  The MobiLink server will authenticate users in the following 
 ways, when ldap_failover_to_std is
  0: The MobiLink server will authenticate the user against LDAP server only.  
 If the user cannot be authenticated against a LDAP server, the MobiLink server 
 will fail the sync request, regardless of the types of the errors;
  1: The MobiLink server will authenticate the user using the standard script-based 
 user authentication, if and only if the LDAP server(s) are not available.  
 The authentication status, 6000 will be passed to the user authentication 
 scripts, if the LDAP servers are not available;
  2: The MobiLink server will authenticate the user against a LDAP server 
 first and then authenticates the user with the standard script-based user 
 authentication, no matter if the user is authenticated or not with the LDAP 
 server.  The MobiLink server will pass one of the following values as a user 
 authentication status to the scripts:
                1000:     if the user is authenticated against the LDAP server;
                4000:     if the user is not authenticated against the LDAP 
 server; or
                6000:     if the LDAP servers are not available.
 
 b) User authentication using a default authentication policy:
 The MobiLink server now supports user authentication against a LDAP server 
 using a default user authentication policy.  The default policy name can 
 be specified from the new MobiLink server command option: -zup  
 "set default policy name for user authentication (implies -zu+, cannot 
 be used with -zu-)"
 When a policy name is specified on the MobiLink server command line with 
 this new option, any new MobiLink users that aren’t in the ml_user table 
 will be first authenticated against the LDAP server using this default policy, 
 and then optionally calls the user authentication scripts, if the ldap_failover_to_std 
 property for the default policy is configured with a value of 1 or 2.  If 
 the user is fully authenticated, it will be added into the ml_user table 
 and the same user authentication policy will then be used to authenticate 
 this user later.  This new command line option implies –zu+ and it cannot 
 be used with –zu-.  The MobiLink server will complain, if both –zup and –zu- 
 are given on the command line.
 Please note: the given default user authentication policy name must exist 
 in the ml_user_auth_policy table , otherwise the MobiLink server will complain 
 and refuses to start.
 
 c) The MobiLink user password will be hashed and stored in the ml_user table 
 in the consolidated database, if  and only if the ldap_failover_to_std property 
 is configured with a value of 1 or 2.  The password will not be saved, if 
 this property is set to 0.
  MobiLink - iAS Branded ODBC Drivers

    ================(Build #1733  - Engineering Case #751198)================

 The SQL Anywhere Oracle ODBC driver now supports the following new features 
 when the database is running on an Oracle 12.1 server and the OCI library 
 is from the Oracle 12.1 installation:
 - The maximum size of the VARCHAR2, NVARCHAR2, and RAW data types has been 
 increased from 4,000 to 32,767 bytes
 - Implicit result sets can be returned from stored procedures.  However 
 the number of implicit result sets is limited to one per stored procedure.  
 The implicit result set will be detected automatically by the ODBC driver 
 regardless of the setting for the “Procedure returns results or uses VARRAY 
 parameters” option in the DSN used by the ODBC application
 
 When the database is running on an Oracle 12.1 server and the OCI library 
 is from the Oracle 12.1 installation.
  UltraLite - Runtime Libraries

    ================(Build #1760  - Engineering Case #753086)================

 UltraLite now supports Xcode 5 and iOS 7. Version 16 includes 64-bit libraries 
 for the new A7 (arm64) chip along with the 64-bit simulator.
    ================(Build #1716  - Engineering Case #750618)================

 UltraLite is now supported for Windows 8 store applications and Windows Phone 
 8 applications.
 
 Each bottom level directory in the following tree under the SQLA install 
 root contains a WinRT-based component (UltraLite.winmd/UltraLite.dll) that 
 implements the UltraLite API for the noted platform.
 
 UltraLite
 +-- WinRT
     |-- WindowsPhone
     |   +-- 8.0
     |       +-- arm           : Windows Phone 8 devices
     |       +-- x86           : Windows Phone 8 emulator
     +-- Windows
         +-- 8.0
             +-- arm           : Windows RT ARM-based devices
             |-- x64           : Windows 8 store apps for x64 architecture
             +-- x86           : Windows 8 store apps for x86 architecture
 
 Developing UltraLite applications using this software requires SQL Anywhere 
 16 and Microsoft Visual Studio 2012 or later.
 
 Developing Windows Phone applications requires the Windows Phone SDK 8.0, 
 which is available from:
     http://dev.windowsphone.com/en-us/downloadsdk
 
 Windows Phone SDK 8.0 requires Windows 8.  Microsoft lists the complete 
 system requirements at:
     http://www.microsoft.com/en-us/download/details.aspx?id=35471
 
 Developing Windows store applications requires the Windows SDK for Windows 
 8, which is available from:
     http://msdn.microsoft.com/en-us/library/windows/desktop/hh852363.aspx
 
 The UltraLite.WinRT directory under the SQLA samples root contains a Visual 
 Studio 2012 solution for the CustDb sample that appears in various forms 
 for other UltraLite supported platforms.