From 06fbb5b23b24c396be48d46bf1f1d43ab5c7d11f Mon Sep 17 00:00:00 2001 From: Maurilio Longo Date: Mon, 24 Jul 2000 20:49:27 +0000 Subject: [PATCH] 20000724-22:45 GMT+2 Maurilio Longo --- harbour/ChangeLog | 18 +- harbour/contrib/mysql/dbf2mysql.prg | 168 ++++++ harbour/contrib/mysql/mysql.c | 268 +++++++++ harbour/contrib/mysql/mysql.ch | 102 ++++ harbour/contrib/mysql/mysql.h | 205 +++++++ harbour/contrib/mysql/mysql_com.h | 160 +++++ harbour/contrib/mysql/mysql_version.h | 13 + harbour/contrib/mysql/readme.txt | 55 ++ harbour/contrib/mysql/test.prg | 117 ++++ harbour/contrib/mysql/tmysql.prg | 835 ++++++++++++++++++++++++++ 10 files changed, 1940 insertions(+), 1 deletion(-) create mode 100644 harbour/contrib/mysql/dbf2mysql.prg create mode 100644 harbour/contrib/mysql/mysql.c create mode 100644 harbour/contrib/mysql/mysql.ch create mode 100644 harbour/contrib/mysql/mysql.h create mode 100644 harbour/contrib/mysql/mysql_com.h create mode 100644 harbour/contrib/mysql/mysql_version.h create mode 100644 harbour/contrib/mysql/readme.txt create mode 100644 harbour/contrib/mysql/test.prg create mode 100644 harbour/contrib/mysql/tmysql.prg diff --git a/harbour/ChangeLog b/harbour/ChangeLog index 94cfd0e88f..3b112154df 100644 --- a/harbour/ChangeLog +++ b/harbour/ChangeLog @@ -1,3 +1,19 @@ +20000724-22:45 GMT+2 Maurilio Longo + + + contrib/mysql + + contrib/mysql/mysql.c + + contrib/mysql/mysql.h + + contrib/mysql/mysql_com.h + + contrib/mysql/mysql_version.h + + contrib/mysql/mysql.ch + + contrib/mysql/tmysql.prg + + contrib/mysql/dbf2mysql.prg + + contrib/mysql/test.prg + + contrib/mysql/readme.txt + I've ported mSQL access classes to MySQL. There is a lot of work to do to finish them, but, nonetheless, + in their present state I've been able to use them to port to MySQL a couple of programs written for + mSQL. + 2000-07-24 22:28 UTC+0100 Victor Szakats * rdd/dbcmd.c @@ -18,7 +34,7 @@ * source/rtl/errorapi.c * source/rtl/tobject.prg * source/vm/classes.c - * TOBJECT_ERROR() implemented in Clipper/Harbour instead of C to keep it + * TOBJECT_ERROR() implemented in Clipper/Harbour instead of C to keep it clean. + __errRT_SBASE() internal function added to throw a substitutable (?) runtime error. diff --git a/harbour/contrib/mysql/dbf2mysql.prg b/harbour/contrib/mysql/dbf2mysql.prg new file mode 100644 index 0000000000..69f51788ac --- /dev/null +++ b/harbour/contrib/mysql/dbf2mysql.prg @@ -0,0 +1,168 @@ +/* + * $Id$ + */ + +/* + * Harbour Project source code: + * dbf2mysql.prg - converts a .dbf file into a MySQL table + * + * Copyright 2000 Maurilio Longo + * www - http://www.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 of the License, or + * (at your option) any later version, with one exception: + * + * The exception is that if you link the Harbour Runtime Library (HRL) + * and/or the Harbour Virtual Machine (HVM) 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 HRL + * and/or HVM code into it. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA (or visit + * their web site at http://www.gnu.org/). + * + */ + +#include "inkey.ch" + +procedure main(c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) + + local cTok, nTok := 1 + local cHostName := "localhost" + local cUser := "root" + local cPassWord := "" + local cDataBase, cTable, cFile + local aDbfStruct, i + local lCreateTable := .F. + local oServer, oTable, oRecord + + SET CENTURY ON + SET EPOCH TO 1960 + + if PCount() < 6 + help() + quit + endif + + i := 1 + // Scan parameters and setup workings + while (i <= PCount()) + + cTok := hb_PValue(i++) + + do case + case cTok == "-h" + cHostName := hb_PValue(i++) + + case cTok == "-d" + cDataBase := hb_PValue(i++) + + case cTok == "-t" + cTable := hb_PValue(i++) + + case cTok == "-f" + cFile := hb_PValue(i++) + + case cTok == "-u" + cUser := hb_PValue(i++) + + case cTok == "-p" + cPassWord := hb_PValue(i++) + + case cTok == "-c" + lCreateTable := .T. + + otherwise + help() + quit + endcase + enddo + + dbUseArea(.T.,, cFile, "dbffile",, .T.) + aDbfStruct := dbffile->(dbStruct()) + + oServer := TMySQLServer():New(cHostName, cUser, cPassWord) + if oServer:NetErr() + ? oServer:Error() + quit + endif + + oServer:SelectDB(cDataBase) + if oServer:NetErr() + ? oServer:Error() + quit + endif + + if lCreateTable + if Ascan(oServer:ListTables(), cTable) > 0 + oServer:DeleteTable(cTable) + if oServer:NetErr() + ? oServer:Error() + quit + endif + endif + oServer:CreateTable(cTable, aDbfStruct) + if oServer:NetErr() + ? oServer:Error() + quit + endif + endif + + // Initialize MySQL table + oTable := oServer:Query("SELECT * FROM " + cTable + " LIMIT 1") + if oTable:NetErr() + Alert(oTable:Error()) + quit + endif + + while !dbffile->(eof()) .AND. Inkey() <> K_ESC + + oRecord := oTable:GetBlankRow() + + for i := 1 to dbffile->(FCount()) + oRecord:FieldPut(i, dbffile->(FieldGet(i))) + next + + oTable:Append(oRecord) + if oTable:NetErr() + Alert(oTable:Error()) + endif + + dbffile->(dbSkip()) + + DevPos(Row(), 1) + if (dbffile->(RecNo()) % 100) == 0 + DevOut("imported recs: " + Str(dbffile->(RecNo()))) + endif + enddo + + dbffile->(dbCloseArea()) + oTable:Destroy() + oServer:Destroy() +return + + +procedure Help() + + ? "dbf2MySQL - dbf file to MySQL table conversion utility" + ? "-h hostname (default: localhost)" + ? "-u user (default: root)" + ? "-p password (default no password)" + ? "-d name of database to use" + ? "-t name of table to add records to" + ? "-c delete existing table and create a new one" + ? "-f name of .dbf file to import" + ? "all parameters but -h -u -p -c are mandatory" + ? "" + +return diff --git a/harbour/contrib/mysql/mysql.c b/harbour/contrib/mysql/mysql.c new file mode 100644 index 0000000000..3609b44bce --- /dev/null +++ b/harbour/contrib/mysql/mysql.c @@ -0,0 +1,268 @@ +/* + * $Id$ + */ + + +/* + * Harbour Project source code: + * MySQL DBMS low level (client api) interface code. + * + * Copyright 2000 Maurilio Longo + * www - http://www.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 of the License, or + * (at your option) any later version, with one exception: + * + * The exception is that if you link the Harbour Runtime Library (HRL) + * and/or the Harbour Virtual Machine (HVM) 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 HRL + * and/or HVM code into it. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA (or visit + * their web site at http://www.gnu.org/). + * + */ + + +/* NOTE: we need this to prevent base types redefinition */ +#define _CLIPDEFS_H + +#include "extend.api" +#include "item.api" +#include "mysql.h" + + +/* NOTE: OS/2 EMX port of MySQL needs libmysqlclient.a from 3.21.33b build which has st and mt + versions of client library. I'm using ST version since harbour is single threaded. You need + also .h files from same distribution +*/ + +HB_FUNC(SQLCONNECT) // MYSQL *mysql_real_connect(MYSQL*, char * host, char * user, char * password, char * db, uint port, char *, uint flags) +{ + MYSQL * mysql = NULL; + + mysql = mysql_real_connect(NULL, _parc(1), _parc(2), _parc(3), 0, NULL, 0); + _retnl((long) mysql); +} + + +HB_FUNC(SQLCLOSE) // void mysql_close(MYSQL *mysql) +{ + mysql_close((MYSQL *)_parnl(1)); + _ret(); +} + + +HB_FUNC(SQLSELECTD) // int mysql_select_db(MYSQL *, char *) +{ + _retnl(mysql_select_db((MYSQL *)_parnl(1), _parc(2))); +} + + +HB_FUNC(SQLQUERY) // int mysql_query(MYSQL *, char *) +{ + _retnl((long) mysql_query((MYSQL *)_parnl(1), _parc(2))); +} + + +HB_FUNC(SQLSTORER) // MYSQL_RES *mysql_store_result(MYSQL *) +{ + _retnl((long) mysql_store_result((MYSQL *)_parnl(1))); +} + + +HB_FUNC(SQLFREER) // void mysql_free_result(MYSQL_RES *) +{ + mysql_free_result((MYSQL_RES *)_parnl(1)); + _ret(); +} + + +HB_FUNC(SQLFETCHR) // MYSQL_ROW *mysql_fetch_row(MYSQL_RES *) +{ + MYSQL_RES *mresult = (MYSQL_RES *)_parnl(1); + int num_fields = mysql_num_fields(mresult); + ITEM aRow = _itemArrayNew(num_fields); + ITEM temp; + MYSQL_ROW mrow; + int i; + + mrow = mysql_fetch_row(mresult); + + for (i = 0; i < num_fields; i++) { + /* if field is not empty */ + if (mrow[i] != NULL) { + temp = _itemPutC(NULL, mrow[i]); + } else { + temp = _itemPutC(NULL, ""); + } + + _itemArrayPut(aRow, i + 1, temp); + _itemRelease(temp); + } + _itemReturn(aRow); + _itemRelease(aRow); +} + + +HB_FUNC(SQLDATAS) // void mysql_data_seek(MYSQL_RES *, unsigned int) +{ + mysql_data_seek((MYSQL_RES *)_parnl(1), (unsigned int)_parni(2)); + _ret(); +} + + +HB_FUNC(SQLNROWS) // my_ulongulong mysql_num_rows(MYSQL_RES *) +{ + /* NOTE: I receive a my_ulongulong which I convert to a long, so I could lose precision */ + _retnl((long)mysql_num_rows(((MYSQL_RES *)_parnl(1)))); +} + + +HB_FUNC(SQLFETCHF) // MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *) +{ + /* NOTE: field structure of MySQL has 8 members as of MySQL 3.22.x */ + ITEM aField = _itemArrayNew(8); + + ITEM temp; + MYSQL_FIELD *mfield; + + mfield = mysql_fetch_field((MYSQL_RES *)_parnl(1)); + + if (!(mfield == NULL)) { + temp = _itemPutC(NULL, mfield->name); + _itemArrayPut(aField, 1, temp); + _itemRelease(temp); + + temp = _itemPutC(NULL, mfield->table); + _itemArrayPut(aField, 2, temp); + _itemRelease(temp); + + temp = _itemPutC(NULL, mfield->def); + _itemArrayPut(aField, 3, temp); + _itemRelease(temp); + + temp = _itemPutNL(NULL, (long)mfield->type); + _itemArrayPut(aField, 4, temp); + _itemRelease(temp); + + temp = _itemPutNL(NULL, mfield->length); + _itemArrayPut(aField, 5, temp); + _itemRelease(temp); + + temp = _itemPutNL(NULL, mfield->max_length); + _itemArrayPut(aField, 6, temp); + _itemRelease(temp); + + temp = _itemPutNL(NULL, mfield->flags); + _itemArrayPut(aField, 7, temp); + _itemRelease(temp); + + temp = _itemPutNL(NULL, mfield->decimals); + _itemArrayPut(aField, 8, temp); + _itemRelease(temp); + + } + _itemReturn(aField); + _itemRelease(aField); + +} + + +HB_FUNC(SQLFSEEK) // MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *, MYSQL_FIELD_OFFSET) +{ + mysql_field_seek((MYSQL_RES *)_parnl(1), (MYSQL_FIELD_OFFSET)_parni(2)); + _ret(); +} + + +HB_FUNC(SQLNUMFI) // unsigned int mysql_num_fields(MYSQL_RES *) +{ + _retnl(mysql_num_fields(((MYSQL_RES *)_parnl(1)))); +} + + +HB_FUNC(SQLLISTF) // MYSQL_RES *mysql_list_fields(MYSQL *, char *); +{ + _retnl((long) mysql_list_fields((MYSQL *)_parnl(1), _parc(2), NULL)); +} + + +HB_FUNC(SQLGETERR) // char *mysql_error(MYSQL *); +{ + _retc(mysql_error((MYSQL *)_parnl(1))); +} + + +HB_FUNC(SQLLISTDB) // MYSQL_RES * mysql_list_dbs(MYSQL *, char * wild); +{ + MYSQL * mysql = (MYSQL *)_parnl(1); + MYSQL_RES * mresult; + MYSQL_ROW mrow; + long nr, i; + ITEM aDBs; + ITEM temp; + + mresult = mysql_list_dbs(mysql, NULL); + nr = mysql_num_rows(mresult); + aDBs = _itemArrayNew(nr); + + for (i = 0; i < nr; i++) { + mrow = mysql_fetch_row(mresult); + temp = _itemPutC(NULL, mrow[0]); + + _itemArrayPut(aDBs, i + 1, temp); + _itemRelease(temp); + } + + mysql_free_result(mresult); + _itemReturn(aDBs); + _itemRelease(aDBs); +} + + +HB_FUNC(SQLLISTTBL) // MYSQL_RES * mysql_list_tables(MYSQL *, char * wild); +{ + MYSQL * mysql = (MYSQL *)_parnl(1); + MYSQL_RES * mresult; + MYSQL_ROW mrow; + long nr, i; + ITEM aTables; + ITEM temp; + + mresult = mysql_list_tables(mysql, NULL); + nr = mysql_num_rows(mresult); + aTables = _itemArrayNew(nr); + + for (i = 0; i < nr; i++) { + + mrow = mysql_fetch_row(mresult); + temp = _itemPutC(NULL, mrow[0]); + + _itemArrayPut(aTables, i + 1, temp); + _itemRelease(temp); + } + + mysql_free_result(mresult); + _itemReturn(aTables); + _itemRelease(aTables); +} + + +// returns bitwise and of first parameter with second +HB_FUNC(SQLAND) +{ + _retnl(_parnl(1) & _parnl(2)); +} + diff --git a/harbour/contrib/mysql/mysql.ch b/harbour/contrib/mysql/mysql.ch new file mode 100644 index 0000000000..e4cc828c54 --- /dev/null +++ b/harbour/contrib/mysql/mysql.ch @@ -0,0 +1,102 @@ +/* + * $Id$ + */ + + +/* + * Harbour Project source code: + * MySQL DBMS defines + * These defines are clipper code level equivalent of mysql.h and mysql_com.h + * + * Copyright 2000 Maurilio Longo + * www - http://www.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 of the License, or + * (at your option) any later version, with one exception: + * + * The exception is that if you link the Harbour Runtime Library (HRL) + * and/or the Harbour Virtual Machine (HVM) 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 HRL + * and/or HVM code into it. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA (or visit + * their web site at http://www.gnu.org/). + * + */ + + + +// MySQL field types + +#define MYSQL_DECIMAL_TYPE 0 +// NOTE: TINY is used to map clipper logical values to MySQL tables, so 0 == .F., 1 == .T. +#define MYSQL_TINY_TYPE 1 +#define MYSQL_SHORT_TYPE 2 +#define MYSQL_LONG_TYPE 3 +#define MYSQL_FLOAT_TYPE 4 +#define MYSQL_DOUBLE_TYPE 5 +#define MYSQL_NULL_TYPE 6 +#define MYSQL_TIMESTAMP_TYPE 7 +#define MYSQL_LONGLONG_TYPE 8 +#define MYSQL_INT24_TYPE 9 +#define MYSQL_DATE_TYPE 10 +#define MYSQL_TIME_TYPE 11 +#define MYSQL_DATETIME_TYPE 12 +#define MYSQL_YEAR_TYPE 13 +#define MYSQL_NEWDATE_TYPE 14 +#define MYSQL_ENUMTYPE 247 +#define MYSQL_SET_TYPE 248 +#define MYSQL_TINY_BLOB_TYPE 249 +#define MYSQL_MEDIUM_BLOB_TYPE 250 +#define MYSQL_LONG_BLOB_TYPE 251 +#define MYSQL_BLOB_TYPE 252 +#define MYSQL_VAR_STRING_TYPE 253 +#define MYSQL_STRING_TYPE 254 + + + +// MySQL field structure item number (C level structure is translated +// to a clipper array) + +#define MYSQL_FS_NAME 1 /* Name of column */ +#define MYSQL_FS_TABLE 2 /* Table of column if column was a field */ +#define MYSQL_FS_DEF 3 /* Default value (set by mysql_list_fields) */ +#define MYSQL_FS_TYPE 4 /* Type of field. Se mysql_com.h for types */ +#define MYSQL_FS_LENGTH 5 /* Width of column */ +#define MYSQL_FS_MAXLEN 6 /* Max width of selected set */ +#define MYSQL_FS_FLAGS 7 /* Div flags */ +#define MYSQL_FS_DECIMALS 8 /* Number of decimals in field */ + + +// MySQL field flags + +#define NOT_NULL_FLAG 1 /* Field can't be NULL */ +#define PRI_KEY_FLAG 2 /* Field is part of a primary key */ +#define UNIQUE_KEY_FLAG 4 /* Field is part of a unique key */ +#define MULTIPLE_KEY_FLAG 8 /* Field is part of a key */ +#define BLOB_FLAG 16 /* Field is a blob */ +#define UNSIGNED_FLAG 32 /* Field is unsigned */ +#define ZEROFILL_FLAG 64 /* Field is zerofill */ +#define BINARY_FLAG 128 +/* The following are only sent to new clients */ +#define ENUM_FLAG 256 /* field is an enum */ +#define AUTO_INCREMENT_FLAG 512 /* field is a autoincrement field */ +#define TIMESTAMP_FLAG 1024 /* Field is a timestamp */ +#define PART_KEY_FLAG 16384 /* Intern; Part of some key */ +#define GROUP_FLAG 32768 /* Intern group field */ + + +// Extension to DBS_xxx defines to encompass NOT NULL fields, needed by indexes +#define DBS_NOTNULL 5 /* True if field has to be NOT NULL */ + diff --git a/harbour/contrib/mysql/mysql.h b/harbour/contrib/mysql/mysql.h new file mode 100644 index 0000000000..9a7d5a09eb --- /dev/null +++ b/harbour/contrib/mysql/mysql.h @@ -0,0 +1,205 @@ +/* + * $Id$ + */ + +/* Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB + This file is public domain and comes with NO WARRANTY of any kind */ + +/* defines for libmysql */ + +#ifndef _mysql_h +#define _mysql_h +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _global_h /* If not standard header */ +#include +typedef char my_bool; +#if !defined(__WIN32__) && !defined(WIN32) +#define STDCALL +typedef char byte; +#else +#define STDCALL __stdcall +#endif +typedef char * gptr; + +#ifndef ST_USED_MEM_DEFINED +#define ST_USED_MEM_DEFINED +typedef struct st_used_mem { /* struct for once_alloc */ + struct st_used_mem *next; /* Next block in use */ + unsigned int left; /* memory left in block */ + unsigned int size; /* size of block */ +} USED_MEM; +typedef struct st_mem_root { + USED_MEM *free; + USED_MEM *used; + unsigned int min_malloc; + unsigned int block_size; + void (*error_handler)(void); +} MEM_ROOT; +#endif + +#ifndef Socket_defined +#ifdef __WIN32__ +#define Socket SOCKET +#else +typedef int Socket; +#endif +#endif +#endif +#include "mysql_com.h" +#include "mysql_version.h" + +extern unsigned int mysql_port; +extern char *mysql_unix_port; + +#define IS_PRI_KEY(n) ((n) & PRI_KEY_FLAG) +#define IS_NOT_NULL(n) ((n) & NOT_NULL_FLAG) +#define IS_BLOB(n) ((n) & BLOB_FLAG) +#define IS_NUM(t) ((t) <= FIELD_TYPE_INT24 || (t) == FIELD_TYPE_YEAR) + +typedef struct st_mysql_field { + char *name; /* Name of column */ + char *table; /* Table of column if column was a field */ + char *def; /* Default value (set by mysql_list_fields) */ + enum enum_field_types type; /* Type of field. Se mysql_com.h for types */ + unsigned int length; /* Width of column */ + unsigned int max_length; /* Max width of selected set */ + unsigned int flags; /* Div flags */ + unsigned int decimals; /* Number of decimals in field */ +} MYSQL_FIELD; + +typedef byte **MYSQL_ROW; /* return data as array of strings */ +typedef unsigned int MYSQL_FIELD_OFFSET; /* offset to current field */ + +typedef struct st_mysql_rows { + struct st_mysql_rows *next; /* list of rows */ + MYSQL_ROW data; +} MYSQL_ROWS; + +typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; /* offset to current row */ + +typedef struct st_mysql_data { + unsigned int rows; + unsigned int fields; + MYSQL_ROWS *data; + MEM_ROOT alloc; +} MYSQL_DATA; + +enum mysql_status { MYSQL_STATUS_READY,MYSQL_STATUS_GET_RESULT, + MYSQL_STATUS_USE_RESULT}; + +typedef struct st_mysql { + NET net; /* Communication parameters */ + char *host,*user,*passwd,*unix_socket,*server_version,*host_info, + *info,*db; + unsigned int port,client_flag,server_capabilities; + unsigned int protocol_version; + unsigned int field_count; + unsigned long thread_id; /* Id for connection in server */ + unsigned long affected_rows; + unsigned long insert_id; /* id if insert on table with NEXTNR */ + unsigned long extra_info; /* Used by mysqlshow */ + enum mysql_status status; + MYSQL_FIELD *fields; + MEM_ROOT field_alloc; + my_bool free_me; /* If free in mysql_close */ + my_bool reconnect; /* set to 1 if automatic reconnect */ +} MYSQL; + + +typedef struct st_mysql_res { + unsigned long row_count; + unsigned int field_count, current_field; + MYSQL_FIELD *fields; + MYSQL_DATA *data; + MYSQL_ROWS *data_cursor; + MEM_ROOT field_alloc; + MYSQL_ROW row; /* If unbuffered read */ + MYSQL_ROW current_row; /* buffer to current row */ + unsigned int *lengths; /* column lengths of current row */ + MYSQL *handle; /* for unbuffered reads */ + my_bool eof; /* Used my mysql_fetch_row */ +} MYSQL_RES; + + +#define mysql_num_rows(res) (res)->row_count +#define mysql_num_fields(res) (res)->field_count +#define mysql_eof(res) (res)->eof +#define mysql_fetch_field_direct(res,fieldnr) ((res)->fields[fieldnr]) +#define mysql_fetch_fields(res) (res)->fields +#define mysql_row_tell(res) (res)->data_cursor +#define mysql_field_tell(res) (res)->current_field + +#define mysql_affected_rows(mysql) (mysql)->affected_rows +#define mysql_insert_id(mysql) (mysql)->insert_id +#define mysql_error(mysql) (mysql)->net.last_error +#define mysql_errno(mysql) (mysql)->net.last_errno +#define mysql_info(mysql) (mysql)->info +#define mysql_reload(mysql) mysql_refresh((mysql),REFRESH_GRANT) +#define mysql_thread_id(mysql) (mysql)->thread_id + + /* void STDCALL mysql_init(MYSQL *mysql); */ +MYSQL * STDCALL mysql_connect(MYSQL *mysql, const char *host, + const char *user, const char *passwd); +#if MYSQL_VERSION_ID >= 32200 +MYSQL * STDCALL mysql_real_connect(MYSQL *mysql, const char *host, + const char *user, + const char *passwd, + const char *db, + unsigned int port, + const char *unix_socket, + unsigned int clientflag); +#else +MYSQL * STDCALL mysql_real_connect(MYSQL *mysql, const char *host, + const char *user, + const char *passwd, + unsigned int port, + const char *unix_socket, + unsigned int clientflag); +#endif +void STDCALL mysql_close(MYSQL *sock); +int STDCALL mysql_select_db(MYSQL *mysql, const char *db); +int STDCALL mysql_query(MYSQL *mysql, const char *q); +int STDCALL mysql_real_query(MYSQL *mysql, const char *q, + unsigned int length); +int STDCALL mysql_create_db(MYSQL *mysql, const char *DB); +int STDCALL mysql_drop_db(MYSQL *mysql, const char *DB); +int STDCALL mysql_shutdown(MYSQL *mysql); +int STDCALL mysql_dump_debug_info(MYSQL *mysql); +int STDCALL mysql_refresh(MYSQL *mysql, + unsigned int refresh_options); +int STDCALL mysql_kill(MYSQL *mysql,unsigned long pid); +char * STDCALL mysql_stat(MYSQL *mysql); +char * STDCALL mysql_get_server_info(MYSQL *mysql); +char * STDCALL mysql_get_client_info(void); +char * STDCALL mysql_get_host_info(MYSQL *mysql); +unsigned int STDCALL mysql_get_proto_info(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_list_dbs(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_tables(MYSQL *mysql,const char *wild); +MYSQL_RES * STDCALL mysql_list_fields(MYSQL *mysql, const char *table, + const char *wild); +MYSQL_RES * STDCALL mysql_list_processes(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql); +MYSQL_RES * STDCALL mysql_use_result(MYSQL *mysql); +void STDCALL mysql_free_result(MYSQL_RES *result); +void STDCALL mysql_data_seek(MYSQL_RES *mysql,unsigned int offset); +MYSQL_ROW_OFFSET STDCALL mysql_row_seek(MYSQL_RES *mysql, MYSQL_ROW_OFFSET); +MYSQL_FIELD_OFFSET STDCALL mysql_field_seek(MYSQL_RES *mysql, + MYSQL_FIELD_OFFSET offset); +MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *mysql); +unsigned int * STDCALL mysql_fetch_lengths(MYSQL_RES *mysql); +MYSQL_FIELD * STDCALL mysql_fetch_field(MYSQL_RES *handle); +unsigned int STDCALL mysql_escape_string(char *to,const char *from, + unsigned int from_length); +void STDCALL mysql_debug(char *debug); + +/* new api functions */ + +#define HAVE_MYSQL_REAL_CONNECT + +#ifdef __cplusplus +} +#endif +#endif diff --git a/harbour/contrib/mysql/mysql_com.h b/harbour/contrib/mysql/mysql_com.h new file mode 100644 index 0000000000..3b70db9c86 --- /dev/null +++ b/harbour/contrib/mysql/mysql_com.h @@ -0,0 +1,160 @@ +/* + * $Id$ + */ + + +/* Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB + This file is public domain and comes with NO WARRANTY of any kind */ + +/* +** Common definition between mysql server & client +*/ + +#ifndef _mysql_com_h +#define _mysql_com_h +#ifdef __cplusplus +extern "C" { +#endif + +#define NAME_LEN 64 /* Field/table name length */ +#define LOCAL_HOST "localhost" + +#define MYSQL_PORT 3306 /* Alloced by ISI for MySQL */ +#define MYSQL_UNIX_ADDR "\\socket\\mysql.sock" + +enum enum_server_command {COM_SLEEP,COM_QUIT,COM_INIT_DB,COM_QUERY, + COM_FIELD_LIST,COM_CREATE_DB,COM_DROP_DB,COM_REFRESH, + COM_SHUTDOWN,COM_STATISTICS, + COM_PROCESS_INFO,COM_CONNECT,COM_PROCESS_KILL, + COM_DEBUG}; + +#define NOT_NULL_FLAG 1 /* Field can't be NULL */ +#define PRI_KEY_FLAG 2 /* Field is part of a primary key */ +#define UNIQUE_KEY_FLAG 4 /* Field is part of a unique key */ +#define MULTIPLE_KEY_FLAG 8 /* Field is part of a key */ +#define BLOB_FLAG 16 /* Field is a blob */ +#define UNSIGNED_FLAG 32 /* Field is unsigned */ +#define ZEROFILL_FLAG 64 /* Field is zerofill */ +#define BINARY_FLAG 128 +/* The following are only sent to new clients */ +#define ENUM_FLAG 256 /* field is an enum */ +#define AUTO_INCREMENT_FLAG 512 /* field is a autoincrement field */ +#define TIMESTAMP_FLAG 1024 /* Field is a timestamp */ +#define PART_KEY_FLAG 16384 /* Intern; Part of some key */ +#define GROUP_FLAG 32768 /* Intern group field */ + +#define REFRESH_GRANT 1 /* Refresh grant tables */ +#define REFRESH_LOG 2 /* Start on new log file */ +#define REFRESH_TABLES 4 /* close all tables */ +#define REFRESH_HOSTS 8 /* Flush host cache */ +#define REFRESH_FAST 32768 /* Intern flag */ + +#define CLIENT_LONG_PASSWORD 1 /* new more secure passwords */ +#define CLIENT_FOUND_ROWS 2 /* Found instead of affected rows */ +#define CLIENT_LONG_FLAG 4 /* Get all column flags */ +#define CLIENT_CONNECT_WITH_DB 8 /* One can specify db on connect */ +#define CLIENT_NO_SCHEMA 16 /* Don't allow database.table.column */ + +#define MYSQL_ERRMSG_SIZE 200 +#define NET_READ_TIMEOUT 30 /* Timeout on read */ +#define NET_WRITE_TIMEOUT 60 /* Timeout on write */ +#define NET_WAIT_TIMEOUT 8*60*60 /* Wait for new query */ + +typedef struct st_net { + Socket fd; + int fcntl; + unsigned char *buff,*buff_end,*write_pos; + char last_error[MYSQL_ERRMSG_SIZE]; + unsigned int last_errno,max_packet,timeout,pkt_nr; + my_bool error,return_errno; +} NET; + +#define packet_error ((unsigned int) -1) + +enum enum_field_types { FIELD_TYPE_DECIMAL, FIELD_TYPE_TINY, + FIELD_TYPE_SHORT, FIELD_TYPE_LONG, + FIELD_TYPE_FLOAT, FIELD_TYPE_DOUBLE, + FIELD_TYPE_NULL, FIELD_TYPE_TIMESTAMP, + FIELD_TYPE_LONGLONG,FIELD_TYPE_INT24, + FIELD_TYPE_DATE, FIELD_TYPE_TIME, + FIELD_TYPE_DATETIME, FIELD_TYPE_YEAR, + FIELD_TYPE_NEWDATE, + FIELD_TYPE_ENUM=247, + FIELD_TYPE_SET=248, + FIELD_TYPE_TINY_BLOB=249, + FIELD_TYPE_MEDIUM_BLOB=250, + FIELD_TYPE_LONG_BLOB=251, + FIELD_TYPE_BLOB=252, + FIELD_TYPE_VAR_STRING=253, + FIELD_TYPE_STRING=254 +}; + +#define FIELD_TYPE_CHAR FIELD_TYPE_TINY /* For compability */ +#define FIELD_TYPE_INTERVAL FIELD_TYPE_ENUM /* For compability */ + +extern unsigned long max_allowed_packet; +extern unsigned long net_buffer_length; + +#define net_new_transaction(net) ((net)->pkt_nr=0) +int my_net_init(NET *net,Socket fd); +void net_end(NET *net); +void net_clear(NET *net); +int net_flush(NET *net); +int my_net_write(NET *net,const byte *packet,unsigned int len); +int net_write_command(NET *net,unsigned char command,const byte *packet, + unsigned int len); +int net_real_write(NET *net,const byte *packet,unsigned int len); +unsigned int my_net_read(NET *net); + +struct rand_struct { + unsigned long seed,seed2,max_value; + double max_value_dbl; +}; + + /* The following is for user defined functions */ + +enum Item_result {STRING_RESULT,REAL_RESULT,INT_RESULT}; + +typedef struct st_udf_args +{ + unsigned int arg_count; /* Number of arguments */ + enum Item_result *arg_type; /* Pointer to item_results */ + char **args; /* Pointer to argument */ + unsigned long *lengths; /* Length of string arguments */ +} UDF_ARGS; + + /* This holds information about the result */ + +typedef struct st_udf_init +{ + my_bool maybe_null; /* 1 if function can return NULL */ + unsigned int decimals; /* for real functions */ + unsigned int max_length; /* For string functions */ + char *ptr; /* free pointer for function data */ +} UDF_INIT; + + /* Prototypes to password functions */ + +void randominit(struct rand_struct *rand,unsigned long seed1, + unsigned long seed2); +double rnd(struct rand_struct *rand); +void make_scrambled_password(char *to,const char *password); +void get_salt_from_password(unsigned long *res,const char *password); +char *scramble(char *to,const char *message,const char *password, + my_bool old_ver); +my_bool check_scramble(const char *scramble,const char *message, + unsigned long *salt,my_bool old_ver); +char *get_tty_password(char *opt_message); + +#define NULL_LENGTH ((unsigned long) ~0) /* For net_store_length */ + +#ifdef __WIN32__ +#define socket_errno WSAGetLastError() +#else +#define socket_errno errno +#endif + +#ifdef __cplusplus +} +#endif +#endif diff --git a/harbour/contrib/mysql/mysql_version.h b/harbour/contrib/mysql/mysql_version.h new file mode 100644 index 0000000000..488335cf99 --- /dev/null +++ b/harbour/contrib/mysql/mysql_version.h @@ -0,0 +1,13 @@ +/* + * $Id$ + */ + + +/* Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB + This file is public domain and comes with NO WARRANTY of any kind */ + +/* Version numbers for protocol & mysqld */ + +#define MYSQL_SERVER_VERSION "3.21.33b" +#define FRM_VER 6 +#define MYSQL_VERSION_ID 32133 diff --git a/harbour/contrib/mysql/readme.txt b/harbour/contrib/mysql/readme.txt new file mode 100644 index 0000000000..d71243e863 --- /dev/null +++ b/harbour/contrib/mysql/readme.txt @@ -0,0 +1,55 @@ +/* + * $Id$ + */ + + +24/july/2000 - Harbour MySQL access classes - readme file + + + +This is work in progress, so it has to be fully tested and needs a few more methods to cover MySQL possibilities. + + +This set of files gives you a mean to access a MySQL server, I've developed and tested them on a OS/2 platform, +so changes to Makefile and import library for different platforms are not present. + +In their present state MySQL classes are made up of these files: + +mysql.c: low level wrapper around MySQL client API. It requires libmysqlclient.a library +mysql.h, +mysql_com.h, +mysql_version.h: from MySQL distribution, type and defines of MySQL client api (under OS/2 with OS/2 port of + MySql you need to use the one from 3.21.33b build which is the only one with a single + threaded libmysqlclient.a client library and works ok even with latest MySQL/2 availble). +mysql.ch: clipper level defines of MySQL types +tmysql.prg: MySQL access classes +test.prg: a little test program which wont work for you :-) since it uses a .dbf file not + provided. Use it as a small tutorial of tmysql.prg provided functions. +Makefile: my makefile for OS/2 gcc, you'll surely need to change it to adapt to your needs/platform. + + +tmysql.prg defines four classes: + +TMySQLServer: manages access to a MySQL server and returns an oServer object to which you'll send all your + queries; +TMySQLQuery: a standard query to an oServer with joins. Every query has a GetRow() method + which on every call returns a TMySQLRow object which, in turn, contains requested fields. + Query objects convert MySQL answers (which is an array of strings) to clipper level types. + At present time N (with decimals), L, D, and C clipper types are supported. +TMySQLTable: It's a descendant of a TMySQLQuery and you'll receive it when your query has no joins. + It adds Update(), Append() and Delete() methods which receive a TMySQLRow object and + reflect changes to the MySQL table from which they come. + Please note that TMySQLQuery objects don't have these methods, so, if you want to change + a row received from a TMySQLQuery object you need to construct a valid SQL query and submit + it to an oServer object. +TMySQLRow: Every row returned by a SELECT is converted to a TMySQLRow object. This object handles + fields and has methods to access fields given a field name or position. + +I'm aware that this brief document doesn't explain a lot about MySQL access classes and I'm sorry for that. +I'll try to update it as work on these classes goes by and I'll like to receive feedbak and suggestions +from users (if any :-)) + +Excuse my poor english and happy selecting :-) + +Maurilio Longo - maurilio.longo@libero.it + diff --git a/harbour/contrib/mysql/test.prg b/harbour/contrib/mysql/test.prg new file mode 100644 index 0000000000..8a17f688b8 --- /dev/null +++ b/harbour/contrib/mysql/test.prg @@ -0,0 +1,117 @@ +/* + * $Id$ + */ + +/* + * Harbour Project source code: + * MySQL DBMS test program + * + * Copyright 2000 Maurilio Longo + * www - http://www.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 of the License, or + * (at your option) any later version, with one exception: + * + * The exception is that if you link the Harbour Runtime Library (HRL) + * and/or the Harbour Virtual Machine (HVM) 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 HRL + * and/or HVM code into it. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA (or visit + * their web site at http://www.gnu.org/). + * + */ + +#include "dbstruct.ch" + +procedure main(cArg) + + local oServer, oQuery, oQuery2, oRow, i, aStru + + SET CENTURY ON + SET EPOCH TO 1960 + + oServer := TMySQLServer():New("localhost", "root", "") + if oServer:NetErr() + Alert(oServer:Error()) + endif + + oServer:SelectDB("ims") + oQuery:=oServer:Query("SELECT * from maga limit 10") + oRow := oQuery:GetRow() + + dbUseArea(.T.,, cArg, "wn", .F.) + + if !oServer:DeleteTable("test") + Alert(oServer:Error()) + endif + + aStru := dbStruct() + if oServer:CreateTable("test", aStru) + Alert("test created successfully") + else + Alert(oServer:Error()) + endif + + oQuery:=oServer:Query("SELECT C111, C116, C134 from maga limit 10") + oRow := oQuery:GetRow() + + oServer:Destroy() + + while !wn->(eof()) + + oQuery2 := oServer:Query("SELECT * from test where CODF='" + wn->CODF + "' and CODP='" + wn->CODP + "'") + + if oQuery2:LastRec() > 0 + + ? "found " + + oRow := oQuery2:GetRow() + + oRow:FieldPut(oRow:FieldPos("GIACENZA"), oRow:FieldGet(oRow:FieldPos("GIACENZA")) + wn->GIACENZA) + oRow:FieldPut(oRow:FieldPos("ACQGR"), oRow:FieldGet(oRow:FieldPos("ACQGR")) + wn->ACQGR) + oRow:FieldPut(oRow:FieldPos("ACQDI"), oRow:FieldGet(oRow:FieldPos("ACQDI")) + wn->ACQDI) + + if !oQuery2:Update(oRow) + Alert(oQuery2:Error()) + endif + + else + + ? wn->CODF + " " + wn->CODP + + oRow := oQuery:GetBlankRow() + + oRow:FieldPut(oRow:FieldPos("CODF"), wn->CODF) + oRow:FieldPut(oRow:FieldPos("CODP"), wn->CODP) + oRow:FieldPut(oRow:FieldPos("GIACENZA"), wn->GIACENZA) + oRow:FieldPut(oRow:FieldPos("DATA"), wn->DATA + 365 * 100) + oRow:FieldPut(oRow:FieldPos("ACQGR"), wn->ACQGR) + oRow:FieldPut(oRow:FieldPos("ACQDI"), wn->ACQDI) + + if !oQuery:Append(oRow) + Alert(oQuery:Error()) + endif + + endif + + wn->(dbSkip()) + + enddo + + + wn->(dbCloseArea()) + +return + diff --git a/harbour/contrib/mysql/tmysql.prg b/harbour/contrib/mysql/tmysql.prg new file mode 100644 index 0000000000..1781e6acf0 --- /dev/null +++ b/harbour/contrib/mysql/tmysql.prg @@ -0,0 +1,835 @@ +/* + * $Id$ + */ + +/* + * Harbour Project source code: + * MySQL DBMS classes. + * These classes try to emulate clipper dbXXXX functions on a SQL query + * + * Copyright 2000 Maurilio Longo + * www - http://www.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 of the License, or + * (at your option) any later version, with one exception: + * + * The exception is that if you link the Harbour Runtime Library (HRL) + * and/or the Harbour Virtual Machine (HVM) 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 HRL + * and/or HVM code into it. + * + * 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; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA (or visit + * their web site at http://www.gnu.org/). + * + */ + +#include "hbclass.ch" +#include "common.ch" +#include "dbstruct.ch" +#include "mysql.ch" + + +// Returns an SQL string with clipper value converted ie. Date() -> "'YYYY-MM-DD'" +static function ClipValue2SQL(Value) + + local cValue := "" + + do case + case Valtype(Value) == "N" + cValue := AllTrim(Str(Value)) + + case Valtype(Value) == "D" + if !Empty(Value) + // MySQL dates are like YYYY-MM-DD + cValue := "'" + Str(Year(Value), 4) + "-" + PadL(Month(Value), 2, "0") + "-" + PadL(Day(Value), 2, "0") + "'" + else + cValue := "''" + endif + + case Valtype(Value) == "C" + cValue := "'" + StrTran(Value, "'", "\'") + "'" + + case Valtype(Value) == "L" + cValue := AllTrim(Str(iif(Value == .F., 0, 1))) + + otherwise + cValue := "''" // NOTE: Here we lose values we cannot convert + + endcase + +return cValue + + +// Every single row of an answer +CLASS TMySQLRow + + DATA aRow // a single row of answer + DATA aDirty // array of booleans set to .T. if corresponding field of aRow has been changed + DATA aOldValue // If aDirty[n] is .T. aOldValue[n] keeps a copy of changed value if aRow[n] is part of a primary key + + DATA aFieldStruct // type of each field + DATA 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(nNum) // Same as clipper ones + METHOD FieldPut(nNum, Value) + METHOD FieldName(nPosition) + METHOD FieldPos(cFieldName) + + METHOD MakePrimaryKeyWhere() // returns a WHERE x=y statement which uses primary key (if available) + +ENDCLASS + + +METHOD New(aRow, aFStruct, cTableName) CLASS TMySQLRow + + default cTableName to "" + default aFStruct to {} + + ::cTable := cTableName + ::aFieldStruct := aFStruct + + ::aRow := aRow + ::aFieldStruct := aFStruct + + ::aDirty := Array(Len(::aRow)) + ::aOldValue := Array(Len(::aRow)) + AFill(::aDirty, .F.) + +return Self + + +METHOD FieldGet(nNum) CLASS TMySQLRow + + if nNum > 0 .AND. nNum <= Len(::aRow) + return ::aRow[nNum] + else + return nil + endif + +return + + +METHOD FieldPut(nNum, Value) CLASS TMySQLRow + + if nNum > 0 .AND. nNum <= Len(::aRow) + if Valtype(Value) == Valtype(::aRow[nNum]) .OR. Empty(::aRow[nNum]) + ::aRow[nNum] := Value + ::aDirty[nNum] := .T. + return Value + endif + endif + +return nil + + +// Given a field name returns it's position +METHOD FieldPos(cFieldName) CLASS TMySQLRow + + local cUpperName, nPos := 1 + + cUpperName := Upper(cFieldName) + + /* NOTE: this code block kills harbour if called a few thousand times + nPos := AScan(::aFieldStruct, {|aItem| iif(Upper(aItem[MYSQL_FS_NAME]) == cUpperName, .T., .F.)}) + */ + + while nPos <= Len(::aFieldStruct) + if Upper(::aFieldStruct[nPos][MYSQL_FS_NAME]) == cUpperName + exit + endif + nPos++ + enddo + +return nPos + + +// Returns name of field N +METHOD FieldName(nPosition) CLASS TMySQLRow + + if nPosition >=1 .AND. nPosition <= Len(::aFieldStruct) + return ::aFieldStruct[nPosition][MYSQL_FS_NAME] + else + return "" + endif + +return + + +// returns a WHERE x=y statement which uses primary key (if available) +METHOD MakePrimaryKeyWhere() CLASS TMySQLRow + + local ni, cWhere := " WHERE " + + for nI := 1 to Len(::aFieldStruct) + + // search for fields part of a primary key + if sqlAND(::aFieldStruct[nI][MYSQL_FS_FLAGS], PRI_KEY_FLAG) == PRI_KEY_FLAG + + 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 + + cWhere += " AND " + endif + + next + + // remove last " AND " + cWhere := Left(cWhere, Len(cWhere) - 5) + +return cWhere + +/* ----------------------------------------------------------------------------------------*/ + +// Every single query submitted to MySQL server +CLASS TMySQLQuery + + DATA nSocket // connection handle to MySQL server + DATA nResultHandle // result handle received from MySQL + + DATA cQuery // copy of query that generated this object + + DATA nNumRows // number of rows available on answer NOTE MySQL is 0 based + DATA nCurRow // I'm currently over row number + + DATA nNumFields // how many fields per row + DATA aFieldStruct // type of each field, a copy is here a copy inside each row + + DATA lError // .T. if last operation failed + + METHOD New(nSocket, cQuery) // New query object + METHOD Destroy() + + METHOD GetRow(nRow) // return Row n of answer + + METHOD Skip(nRows) // Same as clipper ones + + METHOD Bof() INLINE ::nCurRow == 1 + METHOD Eof() INLINE ::nCurRow > ::nNumRows + METHOD RecNo() INLINE ::nCurRow + METHOD LastRec() INLINE ::nNumRows + + METHOD FCount() + + METHOD NetErr() INLINE ::lError // Returns .T. if something went wrong + METHOD Error() // Returns textual description of last error and clears ::lError + +ENDCLASS + + +METHOD New(nSocket, cQuery) CLASS TMySQLQuery + + local nI, aField, rc + + ::aFieldStruct := {} + ::nSocket := nSocket + ::cQuery := cQuery + ::nCurRow := 1 + ::lError := .F. + + if (rc := sqlQuery(nSocket, cQuery)) == 0 + + // save result set + ::nResultHandle := sqlStoreR(nSocket) + ::nNumRows := sqlNRows(::nResultHandle) + ::nNumFields := sqlNumFi(::nResultHandle) + + for nI := 1 to ::nNumFields + + aField := sqlFetchF(::nResultHandle) + AAdd(::aFieldStruct, aField) + + next + + else + ::nResultHandle := nil + ::nNumFields := 0 + ::nNumRows := 0 + ::lError := .T. + + endif + +return Self + + +METHOD Skip(nRows) CLASS TMySQLQuery + + // NOTE: MySQL row count starts from 0 + default nRows to 1 + + if nRows == 0 + // No move + + elseif nRows < 0 + // Negative movement + ::nCurRow := Max(::nCurRow - nRows, 1) + + else + // positive movement + ::nCurRow := Min(::nCurRow + nRows, ::nNumRows + 1) + + endif + + sqlDataS(::nResultHandle, ::nCurRow - 1) + +return Self + + +/* Given a three letter month name gives back month number as two char string (ie. Apr -> 04) */ +static function NMonth(cMonthValue) + + static cMonths := {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dec" } + + nMonth := AScan(cMonths, cMonthValue) + +return PadL(nMonth, 2, "0") + + +// Get row n of a query and return it as a TMySQLRow object +METHOD GetRow(nRow) CLASS TMySQLQuery + + local aRow := NIL + local oRow := NIL + local i + + default nRow to 0 + + if ::nResultHandle <> NIL + if nRow >= 1 .AND. nRow <= ::nNumRows + + // NOTE: row count starts from 0 + sqlDataS(::nResultHandle, nRow - 1) + ::nCurRow := nRow + else + ::nCurRow++ + endif + + aRow := sqlFetchR(::nResultHandle, ::nNumFields) + + if aRow <> NIL + + // Convert answer from text field to correct clipper types + for i := 1 to ::nNumFields + do case + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_TINY_TYPE + aRow[i] := iif(Val(aRow[i]) == 0, .F., .T.) + + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_SHORT_TYPE .OR.; + ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_LONG_TYPE + aRow[i] := Val(aRow[i]) + + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_DOUBLE_TYPE .OR.; + ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_FLOAT_TYPE + aRow[i] := Val(aRow[i]) + + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_DATE_TYPE + if Empty(aRow[i]) + aRow[i] := CToD("") + else + // Date format YYYY-MM-DD + aRow[i] := CToD(__StrToken(aRow[i], 2, ",") + "-" + __StrToken(aRow[i], 3, ",") + "-" + __StrToken(aRow[i], 1, ",")) + endif + otherwise + endcase + next + + oRow := TMySQLRow():New(aRow, ::aFieldStruct) + endif + endif + +return iif(aRow == NIL, NIL, oRow) + + +// Free result handle and associated resources +METHOD Destroy() CLASS TMySQLQuery + + sqlFreeR(::nResultHandle) + +return Self + + +METHOD FCount() CLASS TMySQLQuery + +return ::nNumFields + + +METHOD Error() CLASS TMySQLQuery + + ::lError := .F. + +return sqlGetErr(::nSocket) + +/* ----------------------------------------------------------------------------------------*/ + +// 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 +CLASS TMySQLTable FROM TMySQLQuery + + DATA cTable // name of table + + METHOD New(nSocket, cQuery, cTableName) + METHOD GetRow(nRow) + + METHOD Update(oRow) // Gets an oRow and updates changed fields + METHOD Delete(oRow) // Deletes passed row from table + METHOD Append(oRow) // Inserts passed row into table + METHOD GetBlankRow() // Returns an empty row with all available fields empty + +ENDCLASS + + +/* TODO: change to reflect MySQL lack of _rowid field */ +METHOD New(nSocket, cQuery, cTableName) CLASS TMySQLTable + + local cTrimmedQuery, nRes, i, aField + + ::cTable := cTableName + + cTrimmedQuery := AllTrim(cQuery) + + super:New(nSocket, cTrimmedQuery) + +return Self + + +METHOD GetRow(nRow) CLASS TMySQLTable + + local oRow := super:GetRow(nRow) + + if oRow <> NIL + oRow:cTable := ::cTable + endif + +return oRow + + +/* Creates an update query for changed fields and submits it to server */ +METHOD Update(oRow) CLASS TMySQLTable + + local cUpdateQuery := "UPDATE " + ::cTable + " SET " + local i, cField + + // is this a row of this table ? + 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) + + cUpdateQuery += oRow:MakePrimaryKeyWhere() + + if sqlQuery(::nSocket, cUpdateQuery) == 0 + // All values are commited + Afill(oRow:aDirty, .F.) + return .T. + + else + ::lError := .T. + + endif + + endif + +return .F. + + +METHOD Delete(oRow) CLASS TMySQLTable + + local cDeleteQuery := "DELETE FROM " + ::cTable + + // is this a row of this table ? + if oRow:cTable == ::cTable + + cDeleteQuery += oRow:MakePrimaryKeyWhere() + + if sqlQuery(::nSocket, cDeleteQuery) == 1 + return .T. + + else + ::lError := .T. + + endif + + endif + +return .F. + + +// Adds a row with values passed into oRow +METHOD Append(oRow) CLASS TMySQLTable + + local cInsertQuery := "INSERT INTO " + ::cTable + " (" + local i, cField + + // is this a row of this table ? + if oRow:cTable == ::cTable + + // field names + for i := 1 to Len(oRow:aRow) + cInsertQuery += oRow:aFieldStruct[i][MYSQL_FS_NAME] + "," + next + // remove last comma from list + cInsertQuery := Left(cInsertQuery, Len(cInsertQuery) -1) + ") VALUES (" + + // field values + for i := 1 to Len(oRow:aRow) + cInsertQuery += ClipValue2SQL(oRow:aRow[i]) + "," + + next + + // remove last comma from list of values and add closing parenthesis + cInsertQuery := Left(cInsertQuery, Len(cInsertQuery) -1) + ")" + + if sqlQuery(::nSocket, cInsertQuery) == 0 + return .T. + else + ::lError := .T. + endif + + endif + +return .F. + + +METHOD GetBlankRow() CLASS TMySQLTable + + local i + local aRow := Array(::FCount()) + + // crate an array of empty fields + for i := 1 to ::FCount() + + do case + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_STRING_TYPE .OR.; + ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_VAR_STRING_TYPE + aRow[i] := "" + + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_SHORT_TYPE .OR.; + ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_LONG_TYPE + aRow[i] := 0 + + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_TINY_TYPE + aRow[i] := .F. + + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_DOUBLE_TYPE .OR.; + ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_FLOAT_TYPE + aRow[i] := 0.0 + + case ::aFieldStruct[i][MYSQL_FS_TYPE] == MYSQL_DATE_TYPE + aRow[i] := CToD("") + + otherwise + aRow[i] := nil + + endcase + next + + return TMySQLRow():New(aRow, ::aFieldStruct, ::cTable, .F.) + +return nil + + +/* ----------------------------------------------------------------------------------------*/ + +// Every available MySQL server +CLASS TMySQLServer + + DATA nSocket // connection handle to server (currently pointer to a MYSQL structure) + DATA cServer // server name + DATA cDBName // Selected DB + DATA cUser // user accessing db + DATA cPassword // his/her password + DATA lError // .T. if occurred an error + + METHOD New(cServer, cUser, cPassword) // 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) // 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 + +ENDCLASS + + +METHOD New(cServer, cUser, cPassword) CLASS TMySQLServer + + ::cServer := cServer + ::cUser := cUser + ::cPassword := cPassword + ::nSocket := sqlConnect(cServer, cUser, cPassword) + ::lError := .F. + + if ::nSocket == 0 + ::lError := .T. + endif + +return Self + +METHOD Destroy() CLASS TMySQLServer + + sqlClose(::nSocket) + +return Self + + +METHOD SelectDB(cDBName) CLASS TMySQLServer + + if sqlSelectD(::nSocket, cDBName) == 0 + ::cDBName := cDBName + return .T. + else + ::cDBName := "" + 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) CLASS TMySQLServer + + local cCreateQuery := "CREATE TABLE " + cTable + " (" + 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 ", ""), "")} + + for i := 1 to Len(aStruct) + do case + case aStruct[i][DBS_TYPE] == "C" + cCreateQuery += aStruct[i][DBS_NAME] + " char(" + AllTrim(Str(aStruct[i][DBS_LEN])) + ")" + Eval(cNN, aStruct[i]) + "," + + case aStruct[i][DBS_TYPE] == "N" + if aStruct[i][DBS_DEC] == 0 + cCreateQuery += aStruct[i][DBS_NAME] + " int " + Eval(cNN, aStruct[i]) + "," + else + cCreateQuery += aStruct[i][DBS_NAME] + " real " + Eval(cNN, aStruct[i]) + "," + endif + + case aStruct[i][DBS_TYPE] == "D" + cCreateQuery += aStruct[i][DBS_NAME] + " date " + Eval(cNN, aStruct[i]) + "," + + case aStruct[i][DBS_TYPE] == "L" + cCreateQuery += aStruct[i][DBS_NAME] + " tinyint " + Eval(cNN, aStruct[i]) + "," + + otherwise + cCreateQuery += aStruct[i][DBS_NAME] + " char(" + AllTrim(Str(aStruct[i][DBS_LEN])) + ")" + Eval(cNN, aStruct[i]) + "," + + endcase + + next + + // remove last comma from list + cCreateQuery := Left(cCreateQuery, Len(cCreateQuery) -1) + ")" + + if sqlQuery(::nSocket, cCreateQuery) == 0 + return .T. + else + ::lError := .T. + endif + +return .F. + + +METHOD CreateIndex(cName, cTable, aFNames, lUnique) CLASS TMySQLServer + + local cCreateQuery := "CREATE " + local i + + default lUnique to .F. + + if lUnique + cCreateQuery += "UNIQUE INDEX " + else + cCreateQuery += "INDEX " + endif + + cCreateQuery += cName + " ON " + cTable + " (" + + for i := 1 to Len(aFNames) + cCreateQuery += aFNames[i] + "," + next + + // remove last comma from list + cCreateQuery := Left(cCreateQuery, Len(cCreateQuery) -1) + ")" + + if sqlQuery(::nSocket, cCreateQuery) == 0 + return .T. + + endif + +return .F. + + +METHOD DeleteIndex(cName, cTable) CLASS TMySQLServer + + local cDropQuery := "DROP INDEX " + cName + " FROM " + cTable + + if sqlQuery(::nSocket, cDropQuery) == 0 + return .T. + endif + +return .F. + + +METHOD DeleteTable(cTable) CLASS TMySQLServer + + local cDropQuery := "DROP TABLE " + cTable + + if sqlQuery(::nSocket, cDropQuery) == 0 + return .T. + + endif + +return .F. + + +METHOD Query(cQuery) CLASS TMySQLServer + + local oQuery, cTableName, i, cUpperQuery, nNumTables, cToken + + cUpperQuery := Upper(AllTrim(cQuery)) + i := 1 + nNumTables := 0 + + while __StrToken(cUpperQuery, i++, " ") <> "FROM" + enddo + while (cToken := __StrToken(cUpperQuery, i++, " ")) <> "WHERE" .AND. cToken <> "LIMIT" .AND. !Empty(cToken) + cTableName := __StrToken(cQuery, i - 1, " ") + nNumTables++ + enddo + + if nNumTables == 1 + oQuery := TMySQLTable():New(::nSocket, cQuery, cTableName) + else + oQuery := TMySQLQuery():New(::nSocket, cQuery) + endif + + if oQuery:nNumRows < 0 + ::lError := .T. + endif + +return oQuery + + +METHOD Error() CLASS TMySQLServer + + ::lError := .F. + +return sqlGetErr(::nSocket) + + +METHOD ListDBs() CLASS TMySQLServer + + local aList + + aList := sqlListDB(::nSocket) + +return aList + + +METHOD ListTables() CLASS TMySQLServer + + local aList + + aList := sqlListTbl(::nSocket) + +return aList + + +/* TOFIX: Conversion creates a .dbf with fields of wrong dimension (often) */ +METHOD TableStruct(cTable) CLASS TMySQLServer + + local nRes, aField, aStruct, aSField, i + + + aStruct := {} + + /* TODO: rewrite for MySQL + nRes := sqlListF(::nSocket, cTable) + + if nRes > 0 + for i := 1 to sqlNumFi(nRes) + + aField := sqlFetchF(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 + + do case + case aField[MSQL_FS_TYPE] == MSQL_INT_TYPE + aSField[DBS_TYPE] := "N" + aSField[DBS_LEN] := 11 + + case aField[MSQL_FS_TYPE] == MSQL_UINT_TYPE + aSField[DBS_TYPE] := "L" + aSField[DBS_LEN] := 1 + + case aField[MSQL_FS_TYPE] == MSQL_CHAR_TYPE + aSField[DBS_TYPE] := "C" + aSField[DBS_LEN] := aField[MSQL_FS_LENGTH] + + case aField[MSQL_FS_TYPE] == MSQL_DATE_TYPE + aSField[DBS_TYPE] := "D" + aSField[DBS_LEN] := aField[MSQL_FS_LENGTH] + + case aField[MSQL_FS_TYPE] == MSQL_REAL_TYPE + aSField[DBS_TYPE] := "N" + aSField[DBS_LEN] := 12 + aSFIeld[DBS_DEC] := 8 + + otherwise + + endcase + + AAdd(aStruct, aSField) + endif + next + + sqlFreeR(nRes) + + endif*/ + +return aStruct