This site is under construction!
A small component that convert a VFP simple SQL statements into an equivalent ODBC compatible statement. It is very configurable and allows you to reuse your VFP code you put in your SQL queries on DBF tables. More important, it allows you to develop application that can run, without modifications, on more ODBC datasources types.
The compoent is a COM object. You need to create an instance of it in order to use it. You create the object by calling the CreateObject function, passing 'VFPSQLPARSER.PARSER.1' as the argument. The object has a method, Convert(sSQL), that converts an SQL statement. It returns the converted statement. The object is simply a text parser, it does NOT call the server or execute the statement. You still have to use SQLExecute(...) to actually send the statement to the server.
There are three primary uses for the parser:
The convertor it parses the given statement and searches function calls. It then replace the function with a function that is recognized by the server. The recognized functions and they replacements are fully configurable. The parser also recognizes and replaces certain operators, like $, ! or +. The parser is not simply a smarter STRTRAN(), it is a true grammar parser. Functions or operators wich are not configured to be replaced are left as they are.
When parsing a full SQL statement, the parser also does several aditional 'services':
m.oConvertor=createobject("VFPSQLPARSER.PARSER.1")
m.sSQL="SELECT * FROM sometable WHERE 'John'$NAME"
m.sSQL=m.oConvertor.Convert(m.sSQL)
SQLExec(m.hSomeHandle,m.sSQL)
This example will convert the VFP specific operator $ into a function recognized by the server. If the default configuration is left, the conversion will look like SELECT * FROM sometable WHERE {fn LOCATE('John',NAME)}>0. If the supplied Oracle configuration is used the result will be SELECT * FROM sometable WHERE (INSTR(NAME,'John')>0).
m.oConvertor=createobject("VFPSQLPARSER.PARSER.1")
m.sSQL="SELECT * FROM sometable WHERE len(NAME)>5"
m.sSQL=m.oConvertor.Convert(m.sSQL)
SQLExec(m.hSomeHandle,m.sSQL)
This example will convert the VFP specific operator $ into a function recognized by the server. If the default configuration is left, the conversion will look like SELECT * FROM sometable WHERE {fn LENGTH(NAME)}>5. Again, if the supplied Oracle configuration is used the result will be SELECT * FROM sometable WHERE LENGTH(NAME)>0.
m.oConvertor=createobject("VFPSQLPARSER.PARSER.1")
m.sEXP="allt(NAME)+dtoc(BIRTH)"
m.sEXP=m.oConvertor.Convert(normalize(m.sEXP))
m.sSQL="SELECT * FROM sometable order by "+m.sEXP
SQLExec(m.hSomeHandle,m.sSQL)
This example shows how you can convert a separate expression and then use it to build an SQL statement. It also shows how a simple expression can result in more complicated expression. The conversion will result in an expression like {fn CONCAT({fn RTRIM({fn LTRIM(NAME)})},{fn CONVERT(BIRTH,SQL_CHAR)})}. If the Oracle configuration is used, the result will be LTRIM(RTRIM(NAME))||TO_CHAR(BIRTH). Note the use of NORMALIZE before passing the expression to the parser. This is required, because the parser does not uderstand the usual VFP short forms of functions, like allt instead of alltrim.
Note that the parser keeps a reference to a configuration object. any changes made to a configuration object affects the behavior of parsers that are using this configuration. You can use the same configuration object to configure several parsers, altough a typical usage scenario involves a single parser and a single configuration object existing trought the application. More configuration objects make sense if your applications is connected to distinct platform servers.
The parser can be further configured using VFP code to respond to events raised by the parser during a conversion. In order to do this, you'll have to use VFPCOM.DLL (available from MS site) to respond to the events generated by the parser. Both the parser and the configuration object raise events. The configurations object raise the event before the conversion, while the parser is searching for a substitution for a function it found. The parser itself raises the event after the substitution had take place. In the event code, modifying the default conversion found by the parser will result in changing the actual conversion.
If you're using the table name translation service of the parser, you must provide an object to respond to the MapTableName event of the configuration object. Usually, the folowig implementation will sufice:
define class _IConfigurationEvents as custom
...
procedure MapTableName(sTable,sRemote)
sRemote=cursorgetprop('Tables',sTable)
endproc
enddefine
The parser uses by default a new configuration object. for maximum compatibility, the built-in configuration uses ODBC escape functions. For optimal performance, you should use a customized configuration object. You can use the Remove methods to remove any of the buil-in replacements. A new configuration object has the following built-in functions recognized:
LEFT |
{fn LEFT(%1,%2)} |
RIGHT |
{fn RIGHT(%1,%2)} |
AT |
{fn LOCATE(%1,%2)} |
ATC |
{fn LOCATE({fn UCASE(%1)},{fn UCASE(%2)})} |
$ |
({fn LOCATE(%1,%2)}>0) |
+ |
{fn CONCAT(%1,%2)} |
LTRIM |
{fn LTRIM(%1)} |
TRIM |
{fn LTRIM(%1) |
RTRIM |
{fn RTRIM(%1)} |
ALLTRIM |
{fn RTRIM({fn LTRIM(%1)})} |
STRTRAN |
{fn REPLACE(%1,%2,%3)} |
UPPER |
{fn UCASE(%1)} |
LOWER |
{fn LCASE(%1)} |
CHR |
{fn CHAR(%1)} |
ASC |
{fn ASCII(%1)} |
SUBSTR |
{fn SUBSTRING(%1,%2,%3)} |
REPLICATE |
{fn REPEAT(%1,%2)} |
SPACE |
{fn REPEAT(' ',%1)} |
PADR |
{fn LEFT({fn CONCAT(%1,{fn REPEAT(' ',%2)})},%2)} |
PADL |
{fn RIGHT({fn CONCAT({fn REPEAT(' ',%2)},%1)},%2)} |
LEN |
{fn LENGTH(%1)} |
STR |
{fn CONVERT(%1,SQL_CHAR)} |
VAL |
{fn CONVERT(%1,SQL_NUMERIC)} |
CTOD |
{fn CONVERT(%1,SQL_DATE)} |
ABS |
{fn ABS(%1)} |
ACOS |
{fn ACOS(%1)} |
ASIN |
{fn ASIN(%1)} |
ATAN |
{fn ATAN(%1)} |
CEILING |
{fn CEILING(%1)} |
COS |
{fn COS(%1)} |
EXP |
{fn EXP(%1)} |
FLOOR |
{fn FLOOR(%1)} |
LOG |
{fn LOG(%1)} |
LOG10 |
{fn LOG10(%1)} |
MOD |
{fn MOD(%1)} |
PI |
{fn PI()} |
RAND |
{fn RAND(%1)} |
ROUND |
{fn ROUND(%1)} |
SIGN |
{fn SIGN(%1)} |
SIN |
{fn SIN(%1)} |
SQRT |
{fn SQRT(%1)} |
TAN |
{fn TAN(%1)} |
DATE |
{fn CURDATE()} |
TIME |
{fn CURTIME()} |
CDOW |
{fn DAYNAME(%1)} |
DAY |
{fn DAYOFMONTH(%1)} |
HOUR |
{fn HOUR(%1)} |
MINUTE |
{fn MINUTE(%1)} |
MONTH |
{fn MONTH(%1)} |
CMONTH |
{fn MONTHNAME(%1)} |
YEAR |
{fn YEAR(%1)} |
WEEK |
{fn WEEK(%1)} |
DTOC |
{fn CONVERT(%1,SQL_CHAR)} |
TTOC |
{fn CONVERT(%1,SQL_CHAR)} |
TTOD |
{fn CONVERT(%1,SQL_DATE)} |
INLIST |
(%1 IN (%%)) |
EMPTY |
(%1=' ') |
The parser does not support all of VFPs SQL grammar. Most
notably, it does not understand JOIN clauses (maybe I'll add suport for
them...). The SELECT statement clauses must be in a certain order:
SELECT [ALL|DISTINCT] field_list FROM tables_list [where_clause]
[order_by_clause] [group_by_clause] [having_clause]. Even if the where_clause
or any other clause is missing, the order has to be the same.
In order to install the parser, download the latest vfpsqlparser.zip
file, unzip the DLL into a folder were you want the parser to remain and then
register the DLL.
You may use the SQL Parser for free, as long as you comply with the Right of Use provided with the program in the license.txt file.
The method takes one parmeter, the result from SQLGETPROP(nSomeConnection,'ODBChdbc').
E.q.:
nHandle=SQLCONNECT()
oParser=createobject('VFPSQLPARSER.PARSER')
oParser.LoadDriver(SQLGETPROP(m.nHandle,'ODBChdbc'))
? oParser.Covert('CREATE TABLE SomeTable (CharField C(10), DateField D, TimeField T, NumericField N(10), NumericField2 N(10,2), IntegerField I, FloatField F, BitField L, MemoField M, GeneralField G)')
This call produces different results, depending on the ODBC driver is loaded.
For an Oracle database, the result is
CREATE TABLE SomeTable (CharField CHAR(10) ,DateField DATE ,TimeField DATE ,NumericField DECIMAL(10,0) ,NumericField2 DECIMAL(10,2) ,IntegerField ,FloatField ,BitField ,MemoField LONG ,GeneralField LONG RAW ),
while for an MS SQL server, the result is
CREATE TABLE SomeTable (CharField char(10) ,DateField datetime ,TimeField datetime ,NumericField decimal(10,0) ,NumericField2 decimal(10,2) ,IntegerField int ,FloatField ,BitField bit ,MemoField text ,GeneralField image ).
Note how some for data types, the parser could not deduce from the ODBC driver
what type to use (Integer, Float and Logical for Oracle, Float for MS SQL). This
may happen for two reasons: either the server does not support the specific type
(as in the case of Logical types on Oracle), either the driver does not
correctly report the supported data types. In such cases, you may need to use
VFPCOM.DLL binding capabilities to customize the parser behavior.