Files
harbour-core/contrib/hbmysql/tmysql.prg
Viktor Szakats 5a2a287752 2017-09-08 16:00 UTC Viktor Szakats (vszakats users.noreply.github.com)
* *
    * partial sync with the 3.4 fork codebase. These are the things
      synces for the most part:
      - copyright headers
      - grammar/typos in comments and some readmes
      - comment/whitespace/decorations
      - variable scoping in C files
      - DO CASE/SWITCH and some other alternate syntax usage
      - minimal amount of human readable text in strings
      - minor code updates
      - HB_TRACE() void * casts for pointers and few other changes to
        avoid C compiler warnings
      - various other, minor code cleanups
      - only Harbour/C code/headers were touched in src, utils, contrib,
        include. No 3rd party code, no make files, and with just a few
        exceptions, no 'tests' code was touched.
      - certain components were not touched were 3.4 diverged too much
        already, like f.e. hbmk2, hbssl, hbcurl, hbexpat
      - the goal was that no actual program logic should be altered by
        these changes. Except some possible minor exceptions, any such
        change is probably a bug in this patch.
      It's a massive patch, if you find anything broken after it, please
      open an Issue with the details. Build test was done on macOS.
      The goal is make it easier to see what actual code/logic was changed
      in 3.4 compared to 3.2 and to make patches easier to apply in both
      ways.
2017-09-08 16:25:13 +00:00

1591 lines
46 KiB
Plaintext

/*
* MySQL DBMS classes.
* These classes try to emulate Cl*pper db*() functions on a SQL query
*
* Copyright 2000 Maurilio Longo <maurilio.longo@libero.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file LICENSE.txt. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/).
*
* As a special exception, the Harbour Project gives permission for
* additional uses of the text contained in its release of Harbour.
*
* The exception is that, if you link the Harbour libraries with other
* files to produce an executable, this does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* Your use of that executable is in no way restricted on account of
* linking the Harbour library code into it.
*
* This exception does not however invalidate any other reasons why
* the executable file might be covered by the GNU General Public License.
*
* This exception applies only to the code released by the Harbour
* Project under the name Harbour. If you copy code from other
* Harbour Project or Free Software Foundation releases into a copy of
* Harbour, as the General Public License permits, the exception does
* not apply to the code that you add in this way. To avoid misleading
* anyone as to the status of such modified files, you must delete
* this exception notice from them.
*
* If you write modifications of your own for Harbour, it is your choice
* whether to permit this exception to apply to your modifications.
* If you do not wish that, delete this exception notice.
*
*/
#include "hbclass.ch"
#include "dbstruct.ch"
#include "mysql.ch"
// Every single row of an answer
CREATE CLASS TMySQLRow
VAR aRow // a single row of answer
VAR aDirty // array of booleans set to .T. if corresponding field of aRow has been changed
VAR aOldValue // If aDirty[ n ] is .T. aOldValue[ n ] keeps a copy of changed value if aRow[ n ] is part of a primary key
VAR aOriValue // Original values ( same as TMySQLtable:aOldValue )
VAR aFieldStruct // type of each field
VAR cTable // Name of table containing this row, empty if TMySQLQuery returned this row
METHOD New( aRow, aFStruct, cTableName ) // Create a new Row object
METHOD FieldGet( cnField ) // Same as Cl*pper ones, but FieldGet() and FieldPut() accept a string as
METHOD FieldPut( cnField, Value ) // field identifier, not only a number
METHOD FieldName( nNum )
METHOD FieldPos( cFieldName )
METHOD FieldLen( nNum ) // Length of field N
METHOD FieldDec( nNum, lFormat ) // How many decimals in field N
METHOD FieldType( nNum ) // Cl*pper type of field N
METHOD MakePrimaryKeyWhere() // returns a WHERE x=y statement which uses primary key (if available)
ENDCLASS
METHOD New( aRow, aFStruct, cTableName ) CLASS TMySQLRow
hb_default( @cTableName, "" )
hb_default( @aFStruct, {} )
::aRow := aRow
::aOriValue := AClone( aRow ) // Original values ( same as TMySQLtable:aOldValue )
::aFieldStruct := aFStruct
::cTable := cTableName
::aDirty := Array( Len( ::aRow ) )
::aOldValue := Array( Len( ::aRow ) )
AFill( ::aDirty, .F. )
RETURN Self
METHOD FieldGet( cnField ) CLASS TMySQLRow
LOCAL nNum := iif( HB_ISSTRING( cnField ), ::FieldPos( cnField ), cnField )
IF nNum > 0 .AND. nNum <= Len( ::aRow )
// Char fields are padded with spaces since a real .dbf field would be
IF ::FieldType( nNum ) == "C"
RETURN PadR( ::aRow[ nNum ], ::aFieldStruct[ nNum ][ MYSQL_FS_LENGTH ] )
ELSE
RETURN ::aRow[ nNum ]
ENDIF
ENDIF
RETURN NIL
METHOD FieldPut( cnField, Value ) CLASS TMySQLRow
LOCAL nNum := iif( HB_ISSTRING( cnField ), ::FieldPos( cnField ), cnField )
IF nNum > 0 .AND. nNum <= Len( ::aRow )
IF ValType( Value ) == ValType( ::aRow[ nNum ] ) .OR. ::aRow[ nNum ] == NIL
// if it is a char field remove trailing spaces
IF HB_ISSTRING( Value )
Value := RTrim( Value )
ENDIF
// Save starting value for this field
IF ! ::aDirty[ nNum ]
::aOldValue[ nNum ] := ::aRow[ nNum ]
::aDirty[ nNum ] := .T.
ENDIF
::aRow[ nNum ] := Value
RETURN Value
ENDIF
ENDIF
RETURN NIL
// Given a field name returns it's position
METHOD FieldPos( cFieldName ) CLASS TMySQLRow
LOCAL cUpperName := Upper( cFieldName )
RETURN AScan( ::aFieldStruct, {| aItem | Upper( aItem[ MYSQL_FS_NAME ] ) == cUpperName } )
// Returns name of field N
METHOD FieldName( nNum ) CLASS TMySQLRow
RETURN iif( nNum >= 1 .AND. nNum <= Len( ::aFieldStruct ), ::aFieldStruct[ nNum ][ MYSQL_FS_NAME ], "" )
METHOD FieldLen( nNum ) CLASS TMySQLRow
RETURN iif( nNum >= 1 .AND. nNum <= Len( ::aFieldStruct ), ::aFieldStruct[ nNum ][ MYSQL_FS_LENGTH ], 0 )
/* lFormat: when .T. method returns number of formatted decimal places from mysql table otherwise _SET_DECIMALS.
lFormat is useful for copying table structure from mysql to dbf
*/
METHOD FieldDec( nNum, lFormat ) CLASS TMySQLRow
hb_default( @lFormat, .F. )
IF nNum >= 1 .AND. nNum <= Len( ::aFieldStruct )
IF ! lFormat .AND. ( ::aFieldStruct[ nNum ][ MYSQL_FS_TYPE ] == MYSQL_TYPE_FLOAT .OR. ;
::aFieldStruct[ nNum ][ MYSQL_FS_TYPE ] == MYSQL_TYPE_DOUBLE )
RETURN Set( _SET_DECIMALS )
ELSE
RETURN ::aFieldStruct[ nNum ][ MYSQL_FS_DECIMALS ]
ENDIF
ENDIF
RETURN 0
METHOD FieldType( nNum ) CLASS TMySQLRow
IF nNum >= 1 .AND. nNum <= Len( ::aFieldStruct )
SWITCH ::aFieldStruct[ nNum ][ MYSQL_FS_TYPE ]
CASE MYSQL_TYPE_TINY
CASE MYSQL_TYPE_SHORT
CASE MYSQL_TYPE_LONG
CASE MYSQL_TYPE_LONGLONG
CASE MYSQL_TYPE_FLOAT
CASE MYSQL_TYPE_DOUBLE
CASE MYSQL_TYPE_NEWDECIMAL
CASE MYSQL_TYPE_INT24
RETURN "N"
CASE MYSQL_TYPE_VAR_STRING
CASE MYSQL_TYPE_STRING
CASE MYSQL_TYPE_DATETIME
RETURN "C"
CASE MYSQL_TYPE_DATE
RETURN "D"
CASE MYSQL_TYPE_BLOB
CASE MYSQL_TYPE_MEDIUM_BLOB
RETURN "M"
ENDSWITCH
ENDIF
RETURN "U"
// returns a WHERE x=y statement which uses primary key (if available)
METHOD MakePrimaryKeyWhere() CLASS TMySQLRow
LOCAL ni, cWhere := ""
FOR nI := 1 TO Len( ::aFieldStruct )
// search for fields part of a primary key
IF hb_bitAnd( ::aFieldStruct[ nI ][ MYSQL_FS_FLAGS ], PRI_KEY_FLAG ) == PRI_KEY_FLAG .OR. ;
hb_bitAnd( ::aFieldStruct[ nI ][ MYSQL_FS_FLAGS ], MULTIPLE_KEY_FLAG ) == MULTIPLE_KEY_FLAG
IF ! Empty( cWhere )
cWhere += " AND "
ENDIF
cWhere += ::aFieldStruct[ nI ][ MYSQL_FS_NAME ] + "="
// if a part of a primary key has been changed, use original value
IF ::aDirty[ nI ]
cWhere += ClipValue2SQL( ::aOldValue[ nI ] )
ELSE
cWhere += ClipValue2SQL( ::aRow[ nI ] )
ENDIF
ENDIF
NEXT
IF ! Empty( cWhere )
cWhere := " WHERE " + cWhere
ENDIF
RETURN cWhere
/* --- */
// Every single query submitted to MySQL server
CREATE CLASS TMySQLQuery
VAR nSocket // connection handle to MySQL server
VAR nResultHandle // result handle received from MySQL
VAR cQuery // copy of query that generated this object
VAR nNumRows // number of rows available on answer NOTE MySQL is 0 based
VAR nCurRow // I'm currently over row number
VAR lBof
VAR lEof
VAR lFieldAsData // Use fields as object DATA. For compatibility
// Names of fields can match name of TMySQLQuery/Table DATAs,
// and it is dangerous. ::lFieldAsData:=.F. can fix it
VAR aRow // Values of fields of current row
VAR nNumFields // how many fields per row
VAR aFieldStruct // type of each field, a copy is here a copy inside each row
VAR lError // .T. if last operation failed
METHOD New( nSocket, cQuery ) // New query object
METHOD Destroy()
METHOD End() INLINE ::Destroy()
METHOD Refresh() // ReExecutes the query (cQuery) so that changes to table are visible
METHOD GetRow( nRow ) // return Row n of answer
METHOD Skip( nRows ) // Same as Cl*pper ones
METHOD Bof() INLINE ::lBof
METHOD Eof() INLINE ::lEof
METHOD RecNo() INLINE ::nCurRow
METHOD LastRec() INLINE ::nNumRows
METHOD GoTop() INLINE ::GetRow( 1 )
METHOD GoBottom() INLINE ::GetRow( ::nNumRows )
METHOD GoTO( nRow ) INLINE ::GetRow( nRow )
METHOD FCount()
METHOD NetErr() INLINE ::lError // Returns .T. if something went wrong
METHOD Error() // Returns textual description of last error and clears ::lError
METHOD FieldName( nNum )
METHOD FieldPos( cFieldName )
METHOD FieldGet( cnField )
METHOD FieldLen( nNum ) // Length of field N
METHOD FieldDec( nNum, lFormat ) // How many decimals in field N
METHOD FieldType( nNum ) // Cl*pper type of field N
ENDCLASS
METHOD New( nSocket, cQuery ) CLASS TMySQLQuery
LOCAL nI, aField
::nSocket := nSocket
::cQuery := cQuery
::lError := .F.
::aFieldStruct := {}
::nCurRow := 1
::nResultHandle := NIL
::nNumFields := 0
::nNumRows := 0
::lBof := .T.
::lEof := .T.
::lFieldAsData := .T. // Use fields as object DATA. For compatibility
::aRow := {} // Values of fields of current row
IF mysql_query( nSocket, cQuery ) == 0
// save result set
IF ! Empty( ::nResultHandle := mysql_store_result( nSocket ) )
::nNumRows := mysql_num_rows( ::nResultHandle )
::nNumFields := mysql_num_fields( ::nResultHandle )
::aRow := Array( ::nNumFields )
FOR nI := 1 TO ::nNumFields
aField := mysql_fetch_field( ::nResultHandle )
AAdd( ::aFieldStruct, aField )
IF ::lFieldAsData
__objAddData( Self, ::aFieldStruct[ nI ][ MYSQL_FS_NAME ] )
ENDIF
NEXT
::getRow( ::nCurRow )
ELSE
::nResultHandle := NIL
ENDIF
ELSE
::lError := .T.
ENDIF
RETURN Self
METHOD Refresh() CLASS TMySQLQuery
// free present result handle
::nResultHandle := NIL
::lError := .F.
IF mysql_query( ::nSocket, ::cQuery ) == 0
// save result set
::nResultHandle := mysql_store_result( ::nSocket )
::nNumRows := mysql_num_rows( ::nResultHandle )
// NOTE: I presume that number of fields doesn't change (that is nobody alters this table) between
// successive refreshes of the same
// But row number could very well change
IF ::nCurRow > ::nNumRows
::nCurRow := ::nNumRows
ENDIF
::getRow( ::nCurRow )
ELSE
#if 0
::aFieldStruct := {}
::nResultHandle := NIL
::nNumFields := 0
::nNumRows := 0
#endif
::lError := .T.
ENDIF
RETURN ! ::lError
METHOD Skip( nRows ) CLASS TMySQLQuery
LOCAL lBof
// NOTE: MySQL row count starts from 0
hb_default( @nRows, 1 )
::lBof := Empty( ::LastRec() )
IF nRows == 0
// No move
ELSEIF nRows < 0
// Negative movement
IF ( ::RecNo() + nRows ) < 1
nRows := - ::RecNo() + 1
// Cl*pper: only SKIP movement can set Bof() to .T.
::lBof := .T. // Try to skip before first record
ENDIF
ELSE
// positive movement
IF ( ::RecNo() + nRows ) > ::LastRec()
nRows := ::LastRec() - ::RecNo() + 1
ENDIF
ENDIF
::nCurRow := ::nCurRow + nRows
// Maintain ::bof() true until next movement
// Cl*pper: only SKIP movement can set Bof() to .T.
lBof := ::Bof()
// mysql_data_seek( ::nResultHandle, ::nCurRow - 1 )
::getRow( ::nCurrow )
IF lBof
::lBof := .T.
ENDIF
RETURN NIL
// Get row n of a query and return it as a TMySQLRow object
METHOD GetRow( nRow ) CLASS TMySQLQuery
LOCAL oRow := NIL
LOCAL i
IF ! HB_ISNUMERIC( nRow )
nRow := ::nCurRow
ENDIF
IF ::nResultHandle != NIL
::lBof := ( Empty( ::LastRec() ) )
IF nRow < 1 .OR. nRow > ::LastRec() // Out of range
// Equal to Clipper behaviour
nRow := ::LastRec() + 1 // LastRec()+1
::nCurRow := ::LastRec() + 1
// ::lEof := .T.
ENDIF
IF nRow >= 1 .AND. nRow <= ::nNumRows
// NOTE: row count starts from 0
mysql_data_seek( ::nResultHandle, nRow - 1 )
::nCurRow := nRow
ENDIF
::lEof := ( ::RecNo() > ::LastRec() )
::aRow := NIL
IF ::Eof()
// Phantom record with empty fields
::aRow := Array( Len( ::aFieldStruct ) )
AFill( ::aRow, "" )
ELSE
::aRow := mysql_fetch_row( ::nResultHandle )
ENDIF
IF ::aRow != NIL
// Convert answer from text field to correct clipper types
FOR i := 1 TO ::nNumFields
SWITCH ::aFieldStruct[ i ][ MYSQL_FS_TYPE ]
CASE MYSQL_TYPE_TINY
CASE MYSQL_TYPE_SHORT
CASE MYSQL_TYPE_LONG
CASE MYSQL_TYPE_LONGLONG
CASE MYSQL_TYPE_INT24
CASE MYSQL_TYPE_NEWDECIMAL
IF ::aRow[ i ] == NIL
::aRow[ i ] := "0"
ENDIF
::aRow[ i ] := Val( ::aRow[ i ] )
EXIT
CASE MYSQL_TYPE_DOUBLE
CASE MYSQL_TYPE_FLOAT
IF ::aRow[ i ] == NIL
::aRow[ i ] := "0"
ENDIF
::aRow[ i ] := Val( ::aRow[ i ] )
EXIT
CASE MYSQL_TYPE_DATE
IF Empty( ::aRow[ i ] )
::aRow[ i ] := hb_SToD()
ELSE
// Date format YYYY-MM-DD
::aRow[ i ] := hb_SToD( Left( ::aRow[ i ], 4 ) + SubStr( ::aRow[ i ], 6, 2 ) + Right( ::aRow[ i ], 2 ) )
ENDIF
EXIT
CASE MYSQL_TYPE_BLOB
// Memo field
EXIT
CASE MYSQL_TYPE_STRING
CASE MYSQL_TYPE_VAR_STRING
// char field
EXIT
CASE MYSQL_TYPE_DATETIME
// DateTime field
EXIT
OTHERWISE
// ? "Unknown type from SQL Server Field: " + hb_ntos( i ) + " is type " + hb_ntos( ::aFieldStruct[ i ][ MYSQL_FS_TYPE ] )
ENDSWITCH
IF ::lFieldAsData
__objSetValueList( Self, { { ::aFieldStruct[ i ][ MYSQL_FS_NAME ], ::aRow[ i ] } } )
ENDIF
NEXT
oRow := TMySQLRow():New( ::aRow, ::aFieldStruct )
ENDIF
ENDIF
RETURN iif( ::aRow == NIL, NIL, oRow )
// Free result handle and associated resources
METHOD Destroy() CLASS TMySQLQuery
::nResultHandle := NIL
RETURN Self
METHOD FCount() CLASS TMySQLQuery
RETURN ::nNumFields
METHOD Error() CLASS TMySQLQuery
::lError := .F.
RETURN mysql_error( ::nSocket )
// Given a field name returns it's position
METHOD FieldPos( cFieldName ) CLASS TMySQLQuery
LOCAL cUpperName, nPos
cUpperName := Upper( cFieldName )
nPos := AScan( ::aFieldStruct, {| aItem | Upper( aItem[ MYSQL_FS_NAME ] ) == cUpperName } )
#if 0
nPos := 0
DO WHILE ++nPos <= Len( ::aFieldStruct )
IF Upper( ::aFieldStruct[ nPos ][ MYSQL_FS_NAME ] ) == cUpperName
EXIT
ENDIF
ENDDO
// I haven't found field name
IF nPos > Len( ::aFieldStruct )
nPos := 0
ENDIF
#endif
RETURN nPos
// Returns name of field N
METHOD FieldName( nNum ) CLASS TMySQLQuery
IF nNum >= 1 .AND. nNum <= Len( ::aFieldStruct )
RETURN ::aFieldStruct[ nNum ][ MYSQL_FS_NAME ]
ENDIF
RETURN ""
METHOD FieldGet( cnField ) CLASS TMySQLQuery
LOCAL nNum, Value
IF HB_ISSTRING( cnField )
nNum := ::FieldPos( cnField )
ELSE
nNum := cnField
ENDIF
IF nNum > 0 .AND. nNum <= ::nNumfields
Value := ::aRow[ nNum ]
// Char fields are padded with spaces since a real .dbf field would be
IF ::FieldType( nNum ) == "C"
RETURN PadR( Value, ::aFieldStruct[ nNum ][ MYSQL_FS_LENGTH ] )
ELSE
RETURN Value
ENDIF
ENDIF
RETURN NIL
METHOD FieldLen( nNum ) CLASS TMySQLQuery
IF nNum >= 1 .AND. nNum <= Len( ::aFieldStruct )
RETURN ::aFieldStruct[ nNum ][ MYSQL_FS_LENGTH ]
ENDIF
RETURN 0
/* lFormat: when .T. method returns number of formatted decimal places from mysql table otherwise _SET_DECIMALS.
lFormat is useful for copying table structure from mysql to dbf */
METHOD FieldDec( nNum, lFormat ) CLASS TMySQLQuery
hb_default( @lFormat, .F. )
IF nNum >= 1 .AND. nNum <= Len( ::aFieldStruct )
IF ! lFormat .AND. ( ::aFieldStruct[ nNum ][ MYSQL_FS_TYPE ] == MYSQL_TYPE_FLOAT .OR. ;
::aFieldStruct[ nNum ][ MYSQL_FS_TYPE ] == MYSQL_TYPE_DOUBLE )
RETURN Set( _SET_DECIMALS )
ELSE
RETURN ::aFieldStruct[ nNum ][ MYSQL_FS_DECIMALS ]
ENDIF
ENDIF
RETURN 0
METHOD FieldType( nNum ) CLASS TMySQLQuery
IF nNum >= 1 .AND. nNum <= Len( ::aFieldStruct )
SWITCH ::aFieldStruct[ nNum ][ MYSQL_FS_TYPE ]
CASE MYSQL_TYPE_TINY
CASE MYSQL_TYPE_SHORT
CASE MYSQL_TYPE_LONG
CASE MYSQL_TYPE_LONGLONG
CASE MYSQL_TYPE_FLOAT
CASE MYSQL_TYPE_DOUBLE
CASE MYSQL_TYPE_NEWDECIMAL
CASE MYSQL_TYPE_INT24
RETURN "N"
CASE MYSQL_TYPE_VAR_STRING
CASE MYSQL_TYPE_STRING
CASE MYSQL_TYPE_DATETIME
RETURN "C"
CASE MYSQL_TYPE_DATE
RETURN "D"
CASE MYSQL_TYPE_BLOB
CASE MYSQL_TYPE_MEDIUM_BLOB
RETURN "M"
ENDSWITCH
ENDIF
RETURN "U"
/* --- */
// A Table is a query without joins; this way I can Insert() e Delete() rows.
// NOTE: it's always a SELECT result, so it will contain a full table only if
// SELECT * FROM ... was issued
CREATE CLASS TMySQLTable INHERIT TMySQLQuery
VAR cTable // name of table
VAR aOldValue // keeps a copy of old value
METHOD New( nSocket, cQuery, cTableName )
METHOD GetRow( nRow )
METHOD Skip( nRow )
METHOD GoTop() INLINE ::GetRow( 1 )
METHOD GoBottom() INLINE ::GetRow( ::nNumRows )
METHOD GoTo( nRow ) INLINE ::GetRow( nRow )
METHOD Update( oRow, lOldRecord, lRefresh ) // Gets an oRow and updates changed fields
METHOD Save() INLINE ::Update()
METHOD Delete( oRow, lOldRecord, lRefresh ) // Deletes passed row from table
METHOD Append( oRow, lRefresh ) // Inserts passed row into table
METHOD GetBlankRow( lSetValues ) // Returns an empty row with all available fields empty
METHOD SetBlankRow() INLINE ::GetBlankRow( .T. ) // Compatibility
METHOD Blank() INLINE ::GetBlankRow()
METHOD FieldPut( cnField, Value ) // field identifier, not only a number
METHOD Refresh()
METHOD MakePrimaryKeyWhere() // returns a WHERE x=y statement which uses primary key (if available)
ENDCLASS
METHOD New( nSocket, cQuery, cTableName ) CLASS TMySQLTable
LOCAL i
::super:New( nSocket, AllTrim( cQuery ) )
::cTable := Lower( cTableName )
::aOldValue := {}
FOR i := 1 TO ::nNumFields
AAdd( ::aOldValue, ::FieldGet( i ) )
NEXT
RETURN Self
METHOD GetRow( nRow ) CLASS TMySQLTable
LOCAL oRow := ::super:GetRow( nRow ), i
IF oRow != NIL
oRow:cTable := ::cTable
ENDIF
::aOldvalue := {}
FOR i := 1 TO ::nNumFields
// ::aOldValue[ i ] := ::FieldGet( i )
AAdd( ::aOldvalue, ::FieldGet( i ) )
NEXT
RETURN oRow
METHOD Skip( nRow ) CLASS TMySQLTable
LOCAL i
::super:skip( nRow )
FOR i := 1 TO ::nNumFields
::aOldValue[ i ] := ::FieldGet( i )
NEXT
RETURN NIL
/* Creates an update query for changed fields and submits it to server */
METHOD Update( oRow, lOldRecord, lRefresh ) CLASS TMySQLTable
LOCAL cUpdateQuery := "UPDATE " + ::cTable + " SET "
LOCAL i
LOCAL ni, cWhere := " WHERE "
hb_default( @lOldRecord, .F. )
// Too many ::refresh() can slow some processes, so we can deactivate it by parameter
hb_default( @lRefresh, .T. )
::lError := .F.
IF oRow == NIL // default Current row
FOR i := 1 TO ::nNumFields
IF !( ::aOldValue[ i ] == ::FieldGet( i ) )
cUpdateQuery += ::aFieldStruct[ i ][ MYSQL_FS_NAME ] + "=" + ClipValue2SQL( ::FieldGet( i ) ) + ","
ENDIF
NEXT
// no Change
IF Right( cUpdateQuery, 4 ) == "SET "
RETURN ! ::lError
ENDIF
// remove last comma
cUpdateQuery := Left( cUpdateQuery, Len( cUpdateQuery ) - 1 )
IF lOldRecord
// based in matching of ALL fields of old record
// WARNING: if there are more than one record of ALL fields matching, all of those records will be changed
FOR nI := 1 TO Len( ::aFieldStruct )
cWhere += ::aFieldStruct[ nI ][ MYSQL_FS_NAME ] + "=" + ClipValue2SQL( ::aOldValue[ nI ] ) + " AND "
NEXT
// remove last " AND "
cWhere := Left( cWhere, Len( cWhere ) - 5 )
cUpdateQuery += cWhere
ELSE
// MakePrimaryKeyWhere is based in fields part of a primary key
cUpdateQuery += ::MakePrimaryKeyWhere()
ENDIF
IF mysql_query( ::nSocket, cUpdateQuery ) == 0
// Clipper maintain same record pointer
// After refresh(), position of current record is often unpredictable
IF lRefresh
::refresh()
ELSE
FOR i := 1 TO ::nNumFields
::aOldValue[ i ] := ::FieldGet( i )
NEXT
ENDIF
ELSE
::lError := .T.
ENDIF
ELSE
IF oRow:cTable == ::cTable
FOR i := 1 TO Len( oRow:aRow )
IF oRow:aDirty[ i ]
cUpdateQuery += oRow:aFieldStruct[ i ][ MYSQL_FS_NAME ] + "=" + ClipValue2SQL( oRow:aRow[ i ] ) + ","
ENDIF
NEXT
// remove last comma
cUpdateQuery := Left( cUpdateQuery, Len( cUpdateQuery ) - 1 )
IF lOldRecord
// based in matching of ALL fields of old record
// WARNING: if there are more than one record of ALL fields matching, all of those records will be changed
FOR nI := 1 TO Len( oRow:aFieldStruct )
cWhere += oRow:aFieldStruct[ nI ][ MYSQL_FS_NAME ] + "=" + ClipValue2SQL( oRow:aOriValue[ nI ] ) + " AND "
NEXT
// remove last " AND "
cWhere := Left( cWhere, Len( cWhere ) - 5 )
cUpdateQuery += cWhere
ELSE
// MakePrimaryKeyWhere is based in fields part of a primary key
cUpdateQuery += oRow:MakePrimaryKeyWhere()
ENDIF
IF mysql_query( ::nSocket, cUpdateQuery ) == 0
// All values are committed
AFill( oRow:aDirty, .F. )
AFill( oRow:aOldValue, NIL )
oRow:aOriValue := AClone( oRow:aRow )
// Cl*pper maintain same record pointer
// After refresh(), position of current record is often unpredictable
IF lRefresh
::refresh()
ENDIF
ELSE
::lError := .T.
ENDIF
ENDIF
ENDIF
RETURN ! ::lError
METHOD Delete( oRow, lOldRecord, lRefresh ) CLASS TMySQLTable
LOCAL cDeleteQuery := "DELETE FROM " + ::cTable, i
LOCAL ni, cWhere := " WHERE "
hb_default( @lOldRecord, .F. )
// Too many ::refresh() can slow some processes, so we can deactivate it by parameter
hb_default( @lRefresh, .T. )
// is this a row of this table ?
IF oRow == NIL
IF lOldRecord
// based in matching of ALL fields of old record
// WARNING: if there are more than one record of ALL fields matching, all of those records will be changed
FOR nI := 1 TO Len( ::aFieldStruct )
cWhere += ::aFieldStruct[ nI ][ MYSQL_FS_NAME ] + "="
// use original value
cWhere += ClipValue2SQL( ::aOldValue[ nI ] )
cWhere += " AND "
NEXT
// remove last " AND "
cWhere := Left( cWhere, Len( cWhere ) - 5 )
cDeleteQuery += cWhere
ELSE
// MakePrimaryKeyWhere is based in fields part of a primary key
cDeleteQuery += ::MakePrimaryKeyWhere()
ENDIF
IF mysql_query( ::nSocket, cDeleteQuery ) == 0
::lError := .F.
// Cl*pper maintain same record pointer
// After refresh(), position of current record is often unpredictable
IF lRefresh
::refresh()
ELSE
FOR i := 1 TO ::nNumFields
::aOldValue[ i ] := ::FieldGet( i )
NEXT
ENDIF
ELSE
::lError := .T.
ENDIF
ELSE
IF oRow:cTable == ::cTable
IF lOldRecord
// based in matching of ALL fields of old record
// WARNING: if there are more than one record of ALL fields matching, all of those records will be changed
FOR nI := 1 TO Len( oRow:aFieldStruct )
cWhere += oRow:aFieldStruct[ nI ][ MYSQL_FS_NAME ] + "="
// use original value
cWhere += ClipValue2SQL( oRow:aOriValue[ nI ] )
cWhere += " AND "
NEXT
// remove last " AND "
cWhere := Left( cWhere, Len( cWhere ) - 5 )
cDeleteQuery += cWhere
ELSE
// MakePrimaryKeyWhere is based in fields part of a primary key
cDeleteQuery += oRow:MakePrimaryKeyWhere()
ENDIF
IF mysql_query( ::nSocket, cDeleteQuery ) == 0
::lError := .F.
// After refresh(), position of current record is often unpredictable
IF lRefresh
::refresh()
ENDIF
ELSE
::lError := .T.
ENDIF
ENDIF
ENDIF
RETURN ! ::lError
// Adds a row with values passed into oRow
METHOD Append( oRow, lRefresh ) CLASS TMySQLTable
LOCAL cInsertQuery := "INSERT INTO " + ::cTable + " ("
LOCAL i
// Too many ::refresh() can slow some processes, so we can deactivate it by parameter
hb_default( @lRefresh, .T. )
IF oRow == NIL // default Current row
// field names
FOR i := 1 TO ::nNumFields
IF ::aFieldStruct[ i ][ MYSQL_FS_FLAGS ] != AUTO_INCREMENT_FLAG
cInsertQuery += ::aFieldStruct[ i ][ MYSQL_FS_NAME ] + ","
ENDIF
NEXT
// remove last comma from list
cInsertQuery := Left( cInsertQuery, Len( cInsertQuery ) - 1 ) + ") VALUES ("
// field values
FOR i := 1 TO ::nNumFields
IF ::aFieldStruct[ i ][ MYSQL_FS_FLAGS ] != AUTO_INCREMENT_FLAG
cInsertQuery += ClipValue2SQL( ::FieldGet( i ) ) + ","
ENDIF
NEXT
// remove last comma from list of values and add closing parenthesis
cInsertQuery := Left( cInsertQuery, Len( cInsertQuery ) - 1 ) + ")"
IF mysql_query( ::nSocket, cInsertQuery ) == 0
::lError := .F.
// Cl*pper add record at end
::nCurRow := ::LastRec() + 1
// After refresh(), position of current record is often unpredictable
IF lRefresh
::refresh()
ELSE
#if 0
/* was same values from FieldGet( i ) ! */
FOR i := 1 TO ::nNumFields
::aOldValue[ i ] := ::FieldGet( i )
NEXT
#endif
ENDIF
RETURN .T.
ELSE
::lError := .T.
ENDIF
ELSE
IF oRow:cTable == ::cTable
// field names
FOR i := 1 TO Len( oRow:aRow )
IF oRow:aFieldStruct[ i ][ MYSQL_FS_FLAGS ] != AUTO_INCREMENT_FLAG
cInsertQuery += oRow:aFieldStruct[ i ][ MYSQL_FS_NAME ] + ","
ENDIF
NEXT
// remove last comma from list
cInsertQuery := Left( cInsertQuery, Len( cInsertQuery ) - 1 ) + ") VALUES ("
// field values
FOR i := 1 TO Len( oRow:aRow )
IF oRow:aFieldStruct[ i ][ MYSQL_FS_FLAGS ] != AUTO_INCREMENT_FLAG
cInsertQuery += ClipValue2SQL( oRow:aRow[ i ] ) + ","
ENDIF
NEXT
// remove last comma from list of values and add closing parenthesis
cInsertQuery := Left( cInsertQuery, Len( cInsertQuery ) - 1 ) + ")"
IF mysql_query( ::nSocket, cInsertQuery ) == 0
::lError := .F.
// All values are committed
AFill( oRow:aDirty, .F. )
AFill( oRow:aOldValue, NIL )
oRow:aOriValue := AClone( oRow:aRow )
// Cl*pper add record at end
::nCurRow := ::LastRec() + 1
// After refresh(), position of current record is often unpredictable
IF lRefresh
::refresh()
ENDIF
RETURN .T.
ELSE
::lError := .T.
ENDIF
ENDIF
ENDIF
RETURN .F.
METHOD GetBlankRow( lSetValues ) CLASS TMySQLTable
LOCAL i
LOCAL aRow := Array( ::nNumFields )
// It is not current row, so do not change it
hb_default( @lSetValues, .F. )
// crate an array of empty fields
FOR i := 1 TO ::nNumFields
SWITCH ::aFieldStruct[ i ][ MYSQL_FS_TYPE ]
CASE MYSQL_TYPE_STRING
CASE MYSQL_TYPE_VAR_STRING
CASE MYSQL_TYPE_BLOB
CASE MYSQL_TYPE_DATETIME
aRow[ i ] := ""
EXIT
CASE MYSQL_TYPE_TINY
CASE MYSQL_TYPE_SHORT
CASE MYSQL_TYPE_LONG
CASE MYSQL_TYPE_LONGLONG
CASE MYSQL_TYPE_INT24
CASE MYSQL_TYPE_NEWDECIMAL
aRow[ i ] := 0
EXIT
CASE MYSQL_TYPE_DOUBLE
CASE MYSQL_TYPE_FLOAT
aRow[ i ] := 0.0
EXIT
CASE MYSQL_TYPE_DATE
aRow[ i ] := hb_SToD()
EXIT
OTHERWISE
aRow[ i ] := NIL
ENDSWITCH
NEXT
IF lSetValues // Assign values as current row values
FOR i := 1 TO ::nNumFields
::FieldPut( i, aRow[ i ] )
::aOldValue[ i ] := aRow[ i ]
NEXT
ENDIF
RETURN TMySQLRow():New( aRow, ::aFieldStruct, ::cTable, .F. )
METHOD FieldPut( cnField, Value ) CLASS TMySQLTable
LOCAL nNum
IF HB_ISSTRING( cnField )
nNum := ::FieldPos( cnField )
ELSE
nNum := cnField
ENDIF
IF nNum > 0 .AND. nNum <= ::nNumFields
IF ValType( Value ) == ValType( ::aRow[ nNum ] ) .OR. ::aRow[ nNum ] == NIL
// if it is a char field remove trailing spaces
IF HB_ISSTRING( Value )
Value := RTrim( Value )
ENDIF
::aRow[ nNum ] := Value
IF ::lFieldAsData
__objSetValueList( Self, { { ::aFieldStruct[ nNum ][ MYSQL_FS_NAME ], Value } } )
ENDIF
RETURN Value
ENDIF
ENDIF
RETURN NIL
METHOD Refresh() CLASS TMySQLTABLE
// free present result handle
::nResultHandle := NIL
::lError := .F.
IF mysql_query( ::nSocket, ::cQuery ) == 0
// save result set
::nResultHandle := mysql_store_result( ::nSocket )
::nNumRows := mysql_num_rows( ::nResultHandle )
// NOTE: I presume that number of fields doesn't change (that is nobody alters this table) between
// successive refreshes of the same
// But row number could very well change
IF ::nCurRow > ::nNumRows
::nCurRow := ::nNumRows
ENDIF
::getRow( ::nCurRow )
ELSE
#if 0
::aFieldStruct := {}
::nResultHandle := NIL
::nNumFields := 0
::nNumRows := 0
::aOldValue := {}
#endif
::lError := .T.
ENDIF
RETURN ! ::lError
// returns a WHERE x=y statement which uses primary key (if available)
METHOD MakePrimaryKeyWhere() CLASS TMySQLTable
LOCAL ni, cWhere := ""
FOR nI := 1 TO Len( ::aFieldStruct )
// search for fields part of a primary key
IF hb_bitAnd( ::aFieldStruct[ nI ][ MYSQL_FS_FLAGS ], PRI_KEY_FLAG ) == PRI_KEY_FLAG .OR. ;
hb_bitAnd( ::aFieldStruct[ nI ][ MYSQL_FS_FLAGS ], MULTIPLE_KEY_FLAG ) == MULTIPLE_KEY_FLAG
IF ! Empty( cWhere )
cWhere += " AND "
ENDIF
cWhere += ::aFieldStruct[ nI ][ MYSQL_FS_NAME ] + "=" + ClipValue2SQL( ::aOldValue[ nI ] )
ENDIF
NEXT
IF ! Empty( cWhere )
cWhere := " WHERE " + cWhere
ENDIF
RETURN cWhere
/* --- */
// Every available MySQL server
CREATE CLASS TMySQLServer
VAR nSocket // connection handle to server (currently pointer to a MYSQL structure)
VAR cServer // server name
VAR nPort // server port
VAR cDBName // Selected DB
VAR cUser // user accessing db
VAR cPassword // his/her password
VAR lError // .T. if occurred an error
VAR cCreateQuery
METHOD New( cServer, cUser, cPassword, nPort, nFlags ) // Opens connection to a server, returns a server object
METHOD Destroy() // Closes connection to server
METHOD SelectDB( cDBName ) // Which data base I will use for subsequent queries
METHOD CreateTable( cTable, aStruct, cPrimaryKey, cUniqueKey, cAuto ) // Create new table using the same syntax of dbCreate()
METHOD DeleteTable( cTable ) // delete table
METHOD TableStruct( cTable ) // returns a structure array compatible with clipper's dbStruct() ones
METHOD CreateIndex( cName, cTable, aFNames, lUnique ) // Create an index (unique) on field name(s) passed as an array of strings aFNames
METHOD DeleteIndex( cName, cTable ) // Delete index cName from cTable
METHOD ListDBs() // returns an array with list of data bases available
METHOD ListTables() // returns an array with list of available tables in current database
METHOD Query( cQuery ) // Gets a textual query and returns a TMySQLQuery or TMySQLTable object
METHOD NetErr() INLINE ::lError // Returns .T. if something went wrong
METHOD Error() // Returns textual description of last error
METHOD CreateDatabase( cDataBase ) // Create an New Mysql Database
METHOD DeleteDatabase( cDataBase ) // Delete database
METHOD sql_Commit() // Commits transaction [mitja]
METHOD sql_Rollback() // Rollbacks transaction [mitja]
METHOD sql_Version() // server version as numeric [mitja]
ENDCLASS
METHOD New( cServer, cUser, cPassword, nPort, nFlags ) CLASS TMySQLServer
::cServer := cServer
::nPort := nPort
::cUser := cUser
::cPassword := cPassword
::nSocket := mysql_real_connect( cServer, cUser, cPassword, nPort, nFlags )
::lError := .F.
IF Empty( ::nSocket )
::lError := .T.
ENDIF
RETURN Self
METHOD Destroy() CLASS TMySQLServer
::nSocket := NIL
RETURN Self
METHOD sql_commit() CLASS TMySQLServer
RETURN mysql_commit( ::nSocket ) == 0
METHOD sql_rollback() CLASS TMySQLServer
RETURN mysql_rollback( ::nSocket ) == 0
METHOD sql_version() CLASS TMySQLServer
RETURN mysql_get_server_version( ::nSocket )
// METHOD SelectDB( cDBName ) CLASS TMySQLServer
//
// IF mysql_select_db( ::nSocket, cDBName ) == 0
// ::cDBName := cDBName
// RETURN .T.
// ELSE
// ::cDBName := ""
// ENDIF
//
// RETURN .F.
// === alterado ===
METHOD SelectDB( cDBName ) CLASS TMySQLServer
::lError := .F.
IF mysql_select_db( ::nSocket, cDBName ) != 0 /* table doesn't exist */
::cDBName := ""
::lError := .T.
ELSE /* table exists */
::cDBName := cDBName
::lError := .F.
RETURN .T.
ENDIF
RETURN .F.
METHOD CreateDatabase( cDataBase ) CLASS TMySQLServer
LOCAL cCreateQuery := "CREATE DATABASE " + Lower( cDatabase )
IF mysql_query( ::nSocket, cCreateQuery ) == 0
RETURN .T.
ENDIF
RETURN .F.
// NOTE: OS/2 port of MySQL is picky about table names, that is if you create a table with
// an upper case name you cannot alter it (for example) using a lower case name, this violates
// OS/2 case insensibility about names
METHOD CreateTable( cTable, aStruct, cPrimaryKey, cUniqueKey, cAuto ) CLASS TMySQLServer
/* NOTE: all table names are created with lower case */
LOCAL i
// returns NOT NULL if extended structure has DBS_NOTNULL field to true
LOCAL cNN := {| aArr | iif( Len( aArr ) > DBS_DEC, iif( aArr[ DBS_NOTNULL ], " NOT NULL ", "" ), "" ) }
::cCreateQuery := "CREATE TABLE " + Lower( cTable ) + " ("
FOR i := 1 TO Len( aStruct )
SWITCH aStruct[ i ][ DBS_TYPE ]
CASE "C"
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " char(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")" + Eval( cNN, aStruct[ i ] ) + iif( aStruct[ i ][ DBS_NAME ] == cPrimaryKey, " NOT NULL ", "" ) + ","
EXIT
CASE "M"
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " text" + Eval( cNN, aStruct[ i ] ) + ","
EXIT
CASE "N"
#if 0
IF aStruct[ i ][ DBS_DEC ] == 0
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " int(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")" + Eval( cNN, aStruct[ i ] ) + iif( aStruct[ i ][ DBS_NAME ] == cPrimaryKey, " NOT NULL ", "" ) + iif( aStruct[ i ][ DBS_NAME ] == cAuto, " auto_increment ", "" ) + ","
ELSE
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " real(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + "," + hb_ntos( aStruct[ i ][ DBS_DEC ] ) + ")" + Eval( cNN, aStruct[ i ] ) + ","
ENDIF
#endif
IF aStruct[ i ][ DBS_DEC ] == 0 .AND. aStruct[ i ][ DBS_LEN ] <= 18
DO CASE
CASE aStruct[ i ][ DBS_LEN ] <= 2
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " tinyint(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")"
CASE aStruct[ i ][ DBS_LEN ] <= 4
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " smallint(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")"
CASE aStruct[ i ][ DBS_LEN ] <= 6
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " mediumint(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")"
CASE aStruct[ i ][ DBS_LEN ] <= 9
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " int(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")"
OTHERWISE
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " bigint(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")"
ENDCASE
::cCreateQuery += Eval( cNN, aStruct[ i ] ) + iif( aStruct[ i ][ DBS_NAME ] == cPrimaryKey, " NOT NULL ", "" ) + iif( aStruct[ i ][ DBS_NAME ] == cAuto, " auto_increment ", "" ) + ","
ELSE
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " real(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + "," + hb_ntos( aStruct[ i ][ DBS_DEC ] ) + ")" + Eval( cNN, aStruct[ i ] ) + ","
ENDIF
EXIT
CASE "D"
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " date " + Eval( cNN, aStruct[ i ] ) + ","
EXIT
CASE "B"
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " mediumblob " + Eval( cNN, aStruct[ i ] ) + ","
EXIT
CASE "I"
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " mediumint " + Eval( cNN, aStruct[ i ] ) + ","
EXIT
OTHERWISE
::cCreateQuery += aStruct[ i ][ DBS_NAME ] + " char(" + hb_ntos( aStruct[ i ][ DBS_LEN ] ) + ")" + Eval( cNN, aStruct[ i ] ) + ","
ENDSWITCH
NEXT
IF cPrimarykey != NIL
::cCreateQuery += " PRIMARY KEY (" + cPrimaryKey + "),"
ENDIF
IF cUniquekey != NIL
::cCreateQuery += " UNIQUE " + cUniquekey + " (" + cUniqueKey + "),"
ENDIF
// remove last comma from list
::cCreateQuery := Left( ::cCreateQuery, Len( ::cCreateQuery ) - 1 ) + ");"
IF mysql_query( ::nSocket, ::cCreateQuery ) == 0
RETURN .T.
ELSE
::lError := .T.
ENDIF
RETURN .F.
METHOD CreateIndex( cName, cTable, aFNames, lUnique ) CLASS TMySQLServer
LOCAL cCreateQuery := "CREATE "
LOCAL i
hb_default( @lUnique, .F. )
IF lUnique
cCreateQuery += "UNIQUE INDEX "
ELSE
cCreateQuery += "INDEX "
ENDIF
cCreateQuery += cName + " ON " + Lower( cTable ) + " ("
FOR i := 1 TO Len( aFNames )
cCreateQuery += aFNames[ i ] + ","
NEXT
// remove last comma from list
cCreateQuery := Left( cCreateQuery, Len( cCreateQuery ) - 1 ) + ")"
IF mysql_query( ::nSocket, cCreateQuery ) == 0
RETURN .T.
ENDIF
RETURN .F.
METHOD DeleteIndex( cName, cTable ) CLASS TMySQLServer
LOCAL cDropQuery := "DROP INDEX " + cName + " FROM " + Lower( cTable )
IF mysql_query( ::nSocket, cDropQuery ) == 0
RETURN .T.
ENDIF
RETURN .F.
METHOD DeleteTable( cTable ) CLASS TMySQLServer
LOCAL cDropQuery := "DROP TABLE " + Lower( cTable )
IF mysql_query( ::nSocket, cDropQuery ) == 0
RETURN .T.
ENDIF
RETURN .F.
METHOD DeleteDatabase( cDataBase ) CLASS TMySQLServer
LOCAL cDropQuery := "DROP DATABASE " + Lower( cDataBase )
IF mysql_query( ::nSocket, cDropQuery ) == 0
RETURN .T.
ENDIF
RETURN .F.
METHOD Query( cQuery ) CLASS TMySQLServer
LOCAL oQuery, cTableName, i, cUpperQuery, nNumTables, cToken
hb_default( @cQuery, "" )
cUpperQuery := Upper( AllTrim( cQuery ) )
i := 1
nNumTables := 1
DO WHILE !( ( cToken := hb_tokenGet( cUpperQuery, i++, " " ) ) == "FROM" ) .AND. ! Empty( cToken )
ENDDO
// first token after "FROM" is a table name
// NOTE: SubSelects ?
cTableName := hb_tokenGet( cUpperQuery, i++, " " )
DO WHILE !( ( cToken := hb_tokenGet( cUpperQuery, i++, " " ) ) == "WHERE" ) .AND. ! Empty( cToken )
// do we have more than one table referenced ?
IF cToken == "," .OR. cToken == "JOIN"
nNumTables++
ENDIF
ENDDO
IF nNumTables == 1
oQuery := TMySQLTable():New( ::nSocket, cQuery, cTableName )
ELSE
oQuery := TMySQLQuery():New( ::nSocket, cQuery )
ENDIF
IF oQuery:NetErr()
::lError := .T.
ENDIF
RETURN oQuery
METHOD Error() CLASS TMySQLServer
::lError := .F.
RETURN iif( Empty( ::nSocket ), "No connection to server", mysql_error( ::nSocket ) )
METHOD ListDBs() CLASS TMySQLServer
RETURN mysql_list_dbs( ::nSocket )
METHOD ListTables() CLASS TMySQLServer
RETURN mysql_list_tables( ::nSocket )
/* FIXME: Conversion creates a .dbf with fields of wrong dimension (often) */
METHOD TableStruct( cTable ) CLASS TMySQLServer
LOCAL aStruct := {}
HB_SYMBOL_UNUSED( cTable )
#if 0
/* TODO: rewrite for MySQL */
LOCAL nRes, aField, aStruct, aSField, i
aStruct := {}
nRes := mysql_list_fields( ::nSocket, cTable )
IF ! Empty( nRes )
FOR i := 1 TO mysql_num_fields( nRes )
aField := mysql_fetch_field( nRes )
aSField := Array( DBS_DEC )
// don't count indexes as real fields
IF aField[ MSQL_FS_TYPE ] <= MSQL_LAST_REAL_TYPE
aSField[ DBS_NAME ] := Left( aField[ MSQL_FS_NAME ], 10 )
aSField[ DBS_DEC ] := 0
SWITCH aField[ MSQL_FS_TYPE ]
CASE MSQL_INT_TYPE
aSField[ DBS_TYPE ] := "N"
aSField[ DBS_LEN ] := 11
EXIT
CASE MSQL_UINT_TYPE
aSField[ DBS_TYPE ] := "L"
aSField[ DBS_LEN ] := 1
EXIT
CASE MSQL_CHAR_TYPE
aSField[ DBS_TYPE ] := "C"
aSField[ DBS_LEN ] := aField[ MSQL_FS_LENGTH ]
EXIT
CASE MSQL_DATE_TYPE
aSField[ DBS_TYPE ] := "D"
aSField[ DBS_LEN ] := aField[ MSQL_FS_LENGTH ]
EXIT
CASE MSQL_REAL_TYPE
aSField[ DBS_TYPE ] := "N"
aSField[ DBS_LEN ] := 12
aSFIeld[ DBS_DEC ] := 8
EXIT
CASE MYSQL_TYPE_MEDIUM_BLOB
aSField[ DBS_TYPE ] := "B"
aSField[ DBS_LEN ] := aField[ MSQL_FS_LENGTH ]
EXIT
CASE FIELD_TYPE_INT24
aSField[ DBS_TYPE ] := "I"
aSField[ DBS_LEN ] := aField[ MSQL_FS_LENGTH ]
aSFIeld[ DBS_DEC ] := aField[ MYSQL_FS_DECIMALS ]
EXIT
ENDSWITCH
AAdd( aStruct, aSField )
ENDIF
NEXT
ENDIF
#endif
RETURN aStruct
// Returns an SQL string with clipper value converted ie. Date() -> "'YYYY-MM-DD'"
STATIC FUNCTION ClipValue2SQL( Value )
SWITCH ValType( Value )
CASE "N"
RETURN hb_ntos( Value )
CASE "D"
IF Empty( Value )
RETURN "''"
ELSE
/* MySQL dates are like YYYY-MM-DD */
RETURN "'" + StrZero( Year( Value ), 4 ) + "-" + StrZero( Month( Value ), 2 ) + "-" + StrZero( Day( Value ), 2 ) + "'"
ENDIF
CASE "C"
CASE "M"
IF Empty( Value )
RETURN "''"
ELSE
RETURN "'" + mysql_escape_string( value ) + "'"
ENDIF
CASE "L"
RETURN iif( Value, "1", "0" )
CASE "U"
RETURN "NULL"
ENDSWITCH
RETURN "''" // NOTE: Here we lose values we cannot convert