* contrib\gtwvg\wvg3stat.prg
* contrib\gtwvg\wvgax.prg
* contrib\gtwvg\wvgcheck.prg
* contrib\gtwvg\wvgclass.prg
* contrib\gtwvg\wvgcombo.prg
* contrib\gtwvg\wvgcrt.prg
* contrib\gtwvg\wvgdarea.prg
* contrib\gtwvg\wvgdatar.prg
* contrib\gtwvg\wvgdlg.prg
* contrib\gtwvg\wvggenrc.prg
* contrib\gtwvg\wvghtmlv.prg
* contrib\gtwvg\wvglistb.prg
* contrib\gtwvg\wvgmenub.prg
* contrib\gtwvg\wvgmle.prg
* contrib\gtwvg\wvgpaint.prg
* contrib\gtwvg\wvgphdlr.prg
* contrib\gtwvg\wvgpushb.prg
* contrib\gtwvg\wvgradio.prg
* contrib\gtwvg\wvgscrlb.prg
* contrib\gtwvg\wvgsle.prg
* contrib\gtwvg\wvgstatb.prg
* contrib\gtwvg\wvgstatc.prg
* contrib\gtwvg\wvgsysw.prg
* contrib\gtwvg\wvgtabpg.prg
* contrib\gtwvg\wvgtoolb.prg
* contrib\gtwvg\wvgtreev.prg
* contrib\gtwvg\wvgwnd.prg
* contrib\hbblat\blatcls.prg
* contrib\hbblink\blinker.prg
* contrib\hbcomm\comm.prg
* contrib\hbct\ctmisc.prg
* contrib\hbct\ctrand.prg
* contrib\hbct\cttime.prg
* contrib\hbct\fcopy.prg
* contrib\hbct\getinfo.prg
* contrib\hbct\getinput.prg
* contrib\hbct\getsecrt.prg
* contrib\hbct\keysec.prg
* contrib\hbct\keytime.prg
* contrib\hbct\kxlat.prg
* contrib\hbct\screen3.prg
* contrib\hbct\scrmark.prg
* contrib\hbct\showtime.prg
* contrib\hbct\tempfile.prg
* contrib\hbfbird\tests\test.prg
* contrib\hbfbird\tfirebrd.prg
* contrib\hbgd\gd.prg
* contrib\hbgd\gdbar.prg
* contrib\hbgd\gdbarcod.prg
* contrib\hbgd\gdchart.prg
* contrib\hbmisc\calldll.prg
* contrib\hbmisc\hbedit.prg
* contrib\hbmisc\udpds.prg
* contrib\hbmysql\tmysql.prg
* contrib\hbmysql\tsqlbrw.prg
* contrib\hbodbc\browodbc.prg
* contrib\hbodbc\todbc.prg
* contrib\hbpgsql\tpostgre.prg
* contrib\hbqt\qtcore\hbqt_misc.prg
* contrib\hbrun\hbrun.prg
* contrib\hbsms\sms.prg
* contrib\hbsqlit3\hdbcsqlt.prg
* contrib\hbtip\cgi.prg
* contrib\hbtip\client.prg
* contrib\hbtip\encb64.prg
* contrib\hbtip\encoder.prg
* contrib\hbtip\ftpcli.prg
* contrib\hbtip\httpcli.prg
* contrib\hbtip\log.prg
* contrib\hbtip\mail.prg
* contrib\hbtip\popcli.prg
* contrib\hbtip\sendmail.prg
* contrib\hbtip\smtpcli.prg
* contrib\hbtip\thtml.prg
* contrib\hbtip\url.prg
* contrib\hbtpathy\telepath.prg
* contrib\hbwin\tests\testprn.prg
* contrib\hbwin\wce_sim.prg
* contrib\hbwin\win_os.prg
* contrib\hbwin\win_reg.prg
* contrib\hbwin\win_tbmp.prg
* contrib\hbwin\win_tprn.prg
* contrib\hbxbp\hbpprocess.prg
* contrib\hbxbp\xbp3state.prg
* contrib\hbxbp\xbpbrowse.prg
* contrib\hbxbp\xbpcheckbox.prg
* contrib\hbxbp\xbpcombobox.prg
* contrib\hbxbp\xbpcrt.prg
* contrib\hbxbp\xbpdataref.prg
* contrib\hbxbp\xbpdialog.prg
* contrib\hbxbp\xbpfiledialog.prg
* contrib\hbxbp\xbpfontdialog.prg
* contrib\hbxbp\xbpgeneric.prg
* contrib\hbxbp\xbpgra.prg
* contrib\hbxbp\xbphtmlviewer.prg
* contrib\hbxbp\xbplistbox.prg
* contrib\hbxbp\xbpmenubar.prg
* contrib\hbxbp\xbpmle.prg
* contrib\hbxbp\xbpparthandler.prg
* contrib\hbxbp\xbppresspace.prg
* contrib\hbxbp\xbpprintdialog.prg
* contrib\hbxbp\xbpprinter.prg
* contrib\hbxbp\xbppushbutton.prg
* contrib\hbxbp\xbpradiobutton.prg
* contrib\hbxbp\xbprtf.prg
* contrib\hbxbp\xbpscrollbar.prg
* contrib\hbxbp\xbpsle.prg
* contrib\hbxbp\xbpspinbutton.prg
* contrib\hbxbp\xbpstatic.prg
* contrib\hbxbp\xbpstatusbar.prg
* contrib\hbxbp\xbpstyle.prg
* contrib\hbxbp\xbptabpage.prg
* contrib\hbxbp\xbptoolbar.prg
* contrib\hbxbp\xbptreeview.prg
* contrib\hbxbp\xbpwindow.prg
* contrib\hbxpp\dbfuncsx.prg
* contrib\hbxpp\runshell.prg
* contrib\hbxpp\thfuncx.prg
* contrib\hbxpp\tthreadx.prg
* contrib\xhb\cstruct.prg
* contrib\xhb\dbgfx.prg
* contrib\xhb\dirrec.prg
* contrib\xhb\dumpvar.prg
* contrib\xhb\hbcomprs.prg
* contrib\xhb\hblog.prg
* contrib\xhb\hjwindow.prg
* contrib\xhb\hterrsys.prg
* contrib\xhb\htmutil.prg
* contrib\xhb\sprintf.prg
* contrib\xhb\tedit.prg
* contrib\xhb\tframe.prg
* contrib\xhb\thtm.prg
* contrib\xhb\trpc.prg
* contrib\xhb\trpccli.prg
* contrib\xhb\ttable.prg
* contrib\xhb\xcstr.prg
* contrib\xhb\xdbmodst.prg
* contrib\xhb\xhberr.prg
* contrib\xhb\xhbmemo.prg
* contrib\xhb\xhbmt.prg
* contrib\xhb\xhbtedit.prg
* contrib\xhb\xhbver.prg
* examples\guestbk\inifiles.prg
* examples\hbdoc2\genhtml.prg
* examples\hbdoc2\hbdoc2.prg
* examples\hbvpdf\hbvpdf.prg
* examples\hbvpdf\hbvpdft.prg
* examples\httpsrv\uhttpd.prg
* examples\rddado\adordd.prg
* IS*() macros converted to HB_IS*() function calls.
(using full uppercase to make them stand out)
% deleted '#include "common.ch"' where possible
1722 lines
49 KiB
Plaintext
1722 lines
49 KiB
Plaintext
/*
|
|
* $Id$
|
|
*/
|
|
|
|
/*
|
|
* Harbour Project source code:
|
|
* MySQL DBMS classes.
|
|
* These classes try to emulate clipper dbXXXX functions on a SQL query
|
|
*
|
|
* Copyright 2000 Maurilio Longo <maurilio.longo@libero.it>
|
|
* www - http://harbour-project.org
|
|
*
|
|
* 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 software; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
|
|
* Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
|
|
*
|
|
* 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 "set.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 clipper 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 ) // Clipper 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
|
|
|
|
IF ! HB_ISSTRING( cTableName )
|
|
cTableName := ""
|
|
ENDIF
|
|
IF ! HB_ISARRAY( aFStruct )
|
|
aFStruct := {}
|
|
ENDIF
|
|
|
|
::aRow := aRow
|
|
//DAVID:
|
|
::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 usefull for copying table structure from mysql to dbf
|
|
*/
|
|
METHOD FieldDec( nNum, lFormat ) CLASS TMySQLRow
|
|
|
|
IF ! HB_ISLOGICAL( lFormat )
|
|
lFormat := .F.
|
|
ENDIF
|
|
|
|
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 clipper ones
|
|
|
|
METHOD Bof() INLINE ::lBof //DAVID: ::nCurRow == 1
|
|
METHOD Eof() INLINE ::lEof //DAVID: ::nCurRow == ::nNumRows
|
|
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 ) // Clipper 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
|
|
//DAVID:
|
|
::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 )
|
|
//DAVID:
|
|
::aRow := Array( ::nNumFields )
|
|
|
|
FOR nI := 1 TO ::nNumFields
|
|
|
|
aField := mysql_fetch_field( ::nResultHandle )
|
|
AAdd( ::aFieldStruct, aField )
|
|
//DAVID:
|
|
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
|
|
/* ::aFieldStruct := {}
|
|
::nResultHandle := NIL
|
|
::nNumFields := 0
|
|
::nNumRows := 0
|
|
*/
|
|
::lError := .T.
|
|
|
|
ENDIF
|
|
|
|
RETURN !::lError
|
|
|
|
|
|
METHOD Skip( nRows ) CLASS TMySQLQuery
|
|
//DAVID:
|
|
LOCAL lbof
|
|
|
|
// NOTE: MySQL row count starts from 0
|
|
IF ! HB_ISNUMERIC( nRows )
|
|
nRows := 1
|
|
ENDIF
|
|
|
|
//DAVID:
|
|
::lBof := Empty( ::LastRec() )
|
|
|
|
IF nRows == 0
|
|
// No move
|
|
|
|
ELSEIF nRows < 0
|
|
// Negative movement
|
|
//DAVID: ::nCurRow := Max( ::nCurRow + nRows, 1 )
|
|
IF ( ( ::recno() + nRows ) + 0 ) < 1
|
|
nRows := - ::recno() + 1
|
|
//Clipper: only SKIP movement can set BOF() to .T.
|
|
::lBof := .T. //Try to skip before first record
|
|
ENDIF
|
|
ELSE
|
|
// positive movement
|
|
//DAVID: ::nCurRow := Min( ::nCurRow + nRows, ::nNumRows )
|
|
IF ( ( ::recno() + nRows ) + 0 ) > ::lastrec()
|
|
nRows := ::lastrec() - ::recno() + 1
|
|
ENDIF
|
|
ENDIF
|
|
|
|
//DAVID:
|
|
::nCurRow := ::nCurRow + nRows
|
|
|
|
//DAVID: maintain ::bof() true until next movement
|
|
//Clipper: only SKIP movement can set BOF() to .T.
|
|
lbof := ::bof()
|
|
|
|
// mysql_data_seek( ::nResultHandle, ::nCurRow - 1 )
|
|
::getRow( ::nCurrow )
|
|
|
|
IF lbof
|
|
::lBof := .T.
|
|
ENDIF
|
|
|
|
//DAVID: DBSKIP() RETURN NIL RETURN ::nCurRow
|
|
RETURN NIL
|
|
|
|
|
|
/* Given a three letter month name gives back month number as two char string (ie. Apr -> 04) */
|
|
STATIC FUNCTION NMonth( cMonthValue )
|
|
|
|
STATIC s_cMonths := { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dec" }
|
|
|
|
RETURN PadL( AScan( s_cMonths, cMonthValue ), 2, "0" )
|
|
|
|
|
|
// Get row n of a query and return it as a TMySQLRow object
|
|
METHOD GetRow( nRow ) CLASS TMySQLQuery
|
|
|
|
//DAVID: replaced by ::aRow LOCAL aRow := NIL
|
|
LOCAL oRow := NIL
|
|
LOCAL i
|
|
|
|
//DAVID: use current row DEFAULT nRow TO 0
|
|
IF ! HB_ISNUMERIC( nRow )
|
|
nRow := ::nCurRow
|
|
ENDIF
|
|
|
|
IF ::nResultHandle != NIL
|
|
|
|
//DAVID:
|
|
::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
|
|
//DAVID: ELSE
|
|
//DAVID: use current row ::nCurRow++
|
|
ENDIF
|
|
|
|
//DAVID:
|
|
::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
|
|
//DAVID:
|
|
IF ::aRow[ i ] == NIL
|
|
::aRow[ i ] := "0"
|
|
ENDIF
|
|
::aRow[ i ] := Val( ::aRow[ i ] )
|
|
EXIT
|
|
|
|
CASE MYSQL_TYPE_DOUBLE
|
|
CASE MYSQL_TYPE_FLOAT
|
|
//DAVID:
|
|
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
|
|
|
|
//DAVID: Alert( "Unknown type from SQL Server Field: " + hb_NToS( i ) + " is type " + hb_NToS( ::aFieldStruct[ i ][ MYSQL_FS_TYPE ] ) )
|
|
// QOUT( "Unknown type from SQL Server Field: " + hb_NToS( i ) + " is type " + hb_NToS( ::aFieldStruct[ i ][ MYSQL_FS_TYPE ] ) )
|
|
|
|
ENDSWITCH
|
|
|
|
//DAVID:
|
|
IF ::lFieldAsData
|
|
__objsetValuelist( Self, { { ::aFieldStruct[ i ][ MYSQL_FS_NAME ], ::aRow[ i ] } } )
|
|
ENDIF
|
|
|
|
NEXT
|
|
|
|
oRow := TMySQLRow():New( ::aRow, ::aFieldStruct )
|
|
|
|
ENDIF
|
|
|
|
ENDIF
|
|
//DAVID: IF ::arow == NIL; msginfo( "::arow NIL" ); 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 )
|
|
|
|
//DAVID: nPos := AScan( ::aFieldStruct, {| aItem | iif( Upper( aItem[ MYSQL_FS_NAME ] ) == cUpperName, .T., .F. ) } )
|
|
nPos := AScan( ::aFieldStruct, {| aItem | Upper( aItem[ MYSQL_FS_NAME ] ) == cUpperName } )
|
|
|
|
/*
|
|
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
|
|
*/
|
|
|
|
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
|
|
//DAVID: Value := __objsendmsg( Self,::aFieldStruct[ nNum ][ MYSQL_FS_NAME ] )
|
|
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 usefull for copying table structure from mysql to dbf
|
|
*/
|
|
METHOD FieldDec( nNum, lFormat ) CLASS TMySQLQuery
|
|
|
|
IF ! HB_ISLOGICAL( lFormat )
|
|
lFormat := .F.
|
|
ENDIF
|
|
|
|
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 FROM 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
|
|
|
|
//DAVID: DBSKIP() RETURN NIL RETURN Self
|
|
RETURN NIL
|
|
|
|
|
|
/* Creates an update query for changed fields and submits it to server */
|
|
//DAVID: lOldRecord, lRefresh added
|
|
METHOD Update( oRow, lOldRecord, lRefresh ) CLASS TMySQLTable
|
|
|
|
LOCAL cUpdateQuery := "UPDATE " + ::cTable + " SET "
|
|
LOCAL i
|
|
//DAVID:
|
|
LOCAL ni, cWhere := " WHERE "
|
|
|
|
IF ! HB_ISLOGICAL( lOldRecord )
|
|
lOldRecord := .F.
|
|
ENDIF
|
|
//DAVID: too many ::refresh() can slow some processes, so we can desactivate it by parameter
|
|
IF ! HB_ISLOGICAL( lRefresh )
|
|
lRefresh := .T.
|
|
ENDIF
|
|
|
|
::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 )
|
|
|
|
//DAVID:
|
|
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
|
|
//DAVID: Clipper maintain same record pointer
|
|
|
|
//DAVID: after refresh(), position of current record is often unpredictable
|
|
IF lRefresh
|
|
::refresh()
|
|
ELSE
|
|
//DAVID: just reset values (?)
|
|
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 )
|
|
|
|
//DAVID:
|
|
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 commited
|
|
Afill( oRow:aDirty, .F. )
|
|
Afill( oRow:aOldValue, NIL )
|
|
|
|
//DAVID:
|
|
oRow:aOriValue := AClone( oRow:aRow )
|
|
|
|
//DAVID: Clipper maintain same record pointer
|
|
|
|
//DAVID: after refresh(), position of current record is often unpredictable
|
|
IF lRefresh
|
|
::refresh()
|
|
ENDIF
|
|
|
|
ELSE
|
|
::lError := .T.
|
|
ENDIF
|
|
ENDIF
|
|
ENDIF
|
|
|
|
RETURN !::lError
|
|
|
|
|
|
//DAVID: lOldRecord, lRefresh added
|
|
METHOD Delete( oRow, lOldRecord, lRefresh ) CLASS TMySQLTable
|
|
|
|
LOCAL cDeleteQuery := "DELETE FROM " + ::cTable , i
|
|
|
|
//DAVID:
|
|
LOCAL ni, cWhere := " WHERE "
|
|
|
|
IF ! HB_ISLOGICAL( lOldRecord )
|
|
lOldRecord := .F.
|
|
ENDIF
|
|
//DAVID: too many ::refresh() can slow some processes, so we can desactivate it by parameter
|
|
IF ! HB_ISLOGICAL( lRefresh )
|
|
lRefresh := .T.
|
|
ENDIF
|
|
|
|
// is this a row of this table ?
|
|
IF oRow == NIL
|
|
|
|
//DAVID:
|
|
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.
|
|
//DAVID: Clipper maintain same record pointer
|
|
//DAVID: ::nCurRow--
|
|
|
|
//DAVID: after refresh(), position of current record is often unpredictable
|
|
IF lRefresh
|
|
::refresh()
|
|
ELSE
|
|
//DAVID: just reset values (?)
|
|
FOR i := 1 TO ::nNumFields
|
|
::aOldValue[ i ] := ::FieldGet( i )
|
|
NEXT
|
|
ENDIF
|
|
ELSE
|
|
::lError := .T.
|
|
ENDIF
|
|
|
|
ELSE
|
|
|
|
IF oRow:cTable == ::cTable
|
|
|
|
//DAVID:
|
|
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.
|
|
|
|
//DAVID: 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
|
|
//DAVID: lRefresh added
|
|
METHOD Append( oRow, lRefresh ) CLASS TMySQLTable
|
|
|
|
LOCAL cInsertQuery := "INSERT INTO " + ::cTable + " ("
|
|
LOCAL i
|
|
|
|
//DAVID: too many ::refresh() can slow some processes, so we can desactivate it by parameter
|
|
IF ! HB_ISLOGICAL( lRefresh )
|
|
lRefresh := .T.
|
|
ENDIF
|
|
|
|
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.
|
|
//DAVID: Clipper add record at end
|
|
::nCurRow := ::lastrec() + 1
|
|
|
|
//DAVID: after refresh(), position of current record is often unpredictable
|
|
IF lRefresh
|
|
::refresh()
|
|
ELSE
|
|
//DAVID: just reset values in memory (?)
|
|
/* was same values from fieldget( i ) !
|
|
FOR i := 1 TO ::nNumFields
|
|
::aOldValue[ i ] := ::FieldGet( i )
|
|
NEXT
|
|
*/
|
|
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
|
|
//DAVID:
|
|
::lError := .F.
|
|
|
|
//DAVID:
|
|
// All values are commited
|
|
AFill( oRow:aDirty, .F. )
|
|
AFill( oRow:aOldValue, NIL )
|
|
|
|
//DAVID:
|
|
oRow:aOriValue := AClone( oRow:aRow )
|
|
|
|
//DAVID: Clipper add record at end
|
|
::nCurRow := ::lastrec() + 1
|
|
|
|
//DAVID: after refresh(), position of current record is often unpredictable
|
|
IF lRefresh
|
|
::refresh()
|
|
ENDIF
|
|
|
|
RETURN .T.
|
|
ELSE
|
|
::lError := .T.
|
|
ENDIF
|
|
ENDIF
|
|
|
|
ENDIF
|
|
|
|
RETURN .F.
|
|
|
|
|
|
//DAVID: lSetValues added METHOD GetBlankRow() CLASS TMySQLTable
|
|
METHOD GetBlankRow( lSetValues ) CLASS TMySQLTable
|
|
|
|
LOCAL i
|
|
LOCAL aRow := Array( ::nNumFields )
|
|
|
|
//DAVID: It is not current row, so do not change it
|
|
IF ! HB_ISLOGICAL( lSetValues )
|
|
lSetValues := .F.
|
|
ENDIF
|
|
|
|
// 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
|
|
|
|
//DAVID:
|
|
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
|
|
|
|
//DAVID: IF Valtype( Value ) == Valtype( ::FieldGet( nNum ) ) .OR. Empty( ::Fieldget( nNum ) )
|
|
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
|
|
|
|
//DAVID:
|
|
::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
|
|
/* ::aFieldStruct := {}
|
|
::nResultHandle := NIL
|
|
::nNumFields := 0
|
|
::nNumRows := 0
|
|
|
|
::aOldValue := {}
|
|
*/
|
|
::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 ) // 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 ) CLASS TMySQLServer
|
|
|
|
::cServer := cServer
|
|
::nPort := nPort
|
|
::cUser := cUser
|
|
::cPassword := cPassword
|
|
::nSocket := mysql_real_connect( cServer, cUser, cPassword, nPort )
|
|
::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 /* tabela nao existe */
|
|
::cDBName := ""
|
|
::lError := .T.
|
|
ELSE /* tabela existe */
|
|
::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 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
|
|
*/
|
|
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
|
|
|
|
IF ! HB_ISLOGICAL( lUnique )
|
|
lUnique := .F.
|
|
ENDIF
|
|
|
|
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
|
|
|
|
IF ! HB_ISSTRING( cQuery )
|
|
cQuery := ""
|
|
ENDIF
|
|
|
|
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 )
|
|
|
|
|
|
/* TOFIX: Conversion creates a .dbf with fields of wrong dimension (often) */
|
|
METHOD TableStruct( cTable ) CLASS TMySQLServer
|
|
|
|
LOCAL aStruct := {}
|
|
|
|
HB_SYMBOL_UNUSED( cTable )
|
|
|
|
/* 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*/
|
|
|
|
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
|