gambas-source-code/main/lib/db/CConnection.c
Benoît Minisini 257bd622a3 Support for returning the newly inserted record. Add 'Connection.FullVersion' property.
[GB.DB]
* NEW: Connection: 'Create()' now has an optional 'Return' boolean argument
  to tell if the 'Result.Update()' method will fill the Result with the
  contents of the newly inserted record. It is based on the optional
  'INSERT INTO ... RETURNING' SQL feature.
* NEW: Connection: The 'FullVersion' property now return the full version
  string of the database server.
* NEW: Database drivers can tell if they do not support the 'RETURNING'
  keyword.
* NEW: Database drivers now must provide the full version string of they
  database server they connect to.
2023-07-14 12:08:11 +02:00

874 lines
18 KiB
C

/***************************************************************************
CConnection.c
(c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.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 program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
***************************************************************************/
#define __CCONNECTION_C
#include "main.h"
#include "CTable.h"
//#include "CView.h"
#include "CDatabase.h"
#include "CUser.h"
#include "CConnection.h"
/***************************************************************************
Connection
***************************************************************************/
static CCONNECTION *_current = NULL;
static SUBCOLLECTION_DESC _databases_desc =
{
".Connection.Databases",
(void *)CDATABASE_get,
(void *)CDATABASE_exist,
(void *)CDATABASE_list,
(void *)CDATABASE_release
};
static SUBCOLLECTION_DESC _users_desc =
{
".Connection.Users",
(void *)CUSER_get,
(void *)CUSER_exist,
(void *)CUSER_list,
(void *)CUSER_release
};
static SUBCOLLECTION_DESC _tables_desc =
{
".Connection.Tables",
(void *)CTABLE_get,
(void *)CTABLE_exist,
(void *)CTABLE_list,
(void *)CTABLE_release
};
/*static GB_SUBCOLLECTION_DESC _views_desc =
{
".ConnectionViews",
(void *)CVIEW_get,
(void *)CVIEW_exist,
(void *)CVIEW_list
};*/
static void open_connection(CCONNECTION *_object)
{
GB_COLLECTION options;
options = GB.GetProperty(THIS, "Options")->_object.value;
if (DB_Open(&THIS->desc, &THIS->driver, &THIS->db, options))
return;
THIS->limit = 0;
THIS->trans = 0;
THIS->db.flags.system = !THIS->desc.name || THIS->driver->Database.IsSystem(&THIS->db, THIS->desc.name);
}
static bool check_opened(CCONNECTION *_object)
{
DB_CurrentDatabase = &THIS->db;
/*if (!THIS->db.handle)
open_connection(THIS);*/
if (!THIS->db.handle)
{
GB.Error("Connection is not opened");
return TRUE;
}
else
return FALSE;
}
#define CHECK_OPEN() \
if (check_opened(THIS)) \
return;
static int get_current(CCONNECTION **current)
{
if (*current == NULL)
{
if (_current == NULL)
{
GB.Error("No current connection");
return TRUE;
}
*current = _current;
}
return FALSE;
}
#define CHECK_DB() \
if (get_current((CCONNECTION **)(void *)&_object)) \
return;
static void close_connection(CCONNECTION *_object)
{
if (!THIS->db.handle)
return;
GB.Unref(POINTER(&THIS->databases));
THIS->databases = NULL;
GB.Unref(POINTER(&THIS->users));
THIS->users = NULL;
GB.Unref(POINTER(&THIS->tables));
THIS->tables = NULL;
THIS->driver->Close(&THIS->db);
GB.FreeString(&THIS->db.charset);
GB.FreeString(&THIS->db.full_version);
THIS->db.handle = NULL;
THIS->driver = NULL;
}
BEGIN_METHOD_VOID(Connection_new)
THIS->db.handle = NULL;
THIS->db.ignore_case = FALSE; // Now case is sensitive by default!
THIS->db.timeout = 20; // Connection timeout is 20 seconds by default
THIS->db.timezone = GB.System.TimeZone();
if (_current == NULL)
_current = THIS;
END_METHOD
BEGIN_METHOD_VOID(Connection_free)
close_connection(THIS);
if (_current == THIS)
_current = NULL;
GB.StoreString(NULL, &THIS->desc.type);
GB.StoreString(NULL, &THIS->desc.host);
GB.StoreString(NULL, &THIS->desc.user);
GB.StoreString(NULL, &THIS->desc.password);
GB.StoreString(NULL, &THIS->desc.name);
GB.StoreString(NULL, &THIS->desc.port);
GB.StoreString(NULL, &THIS->db.charset);
END_METHOD
#define IMPLEMENT(_name, _prop) \
BEGIN_PROPERTY(Connection_##_name) \
\
if (READ_PROPERTY) \
GB.ReturnString(THIS->desc._prop); \
else \
GB.StoreString(PROP(GB_STRING), &THIS->desc._prop); \
\
END_PROPERTY
IMPLEMENT(Type, type)
IMPLEMENT(Host, host)
IMPLEMENT(User, user)
IMPLEMENT(Password, password)
IMPLEMENT(Name, name)
IMPLEMENT(Port, port)
BEGIN_PROPERTY(Connection_Version)
CHECK_DB();
CHECK_OPEN();
GB.ReturnInteger(THIS->db.version);
END_PROPERTY
BEGIN_PROPERTY(Connection_FullVersion)
CHECK_DB();
CHECK_OPEN();
GB.ReturnString(THIS->db.full_version);
END_PROPERTY
BEGIN_PROPERTY(Connection_Timeout)
if (READ_PROPERTY)
GB.ReturnInteger(THIS->db.timeout);
else
THIS->db.timeout = VPROP(GB_INTEGER);
END_PROPERTY
#if 0
BEGIN_PROPERTY(Connection_Timezone)
if (READ_PROPERTY)
GB.ReturnInteger(THIS->db.timezone);
else
THIS->db.timezone = VPROP(GB_INTEGER);
END_PROPERTY
#endif
BEGIN_PROPERTY(Connection_Opened)
CHECK_DB();
GB.ReturnBoolean(THIS->db.handle != NULL);
END_PROPERTY
BEGIN_PROPERTY(Connection_Error)
CHECK_DB();
GB.ReturnInteger(THIS->db.error);
END_PROPERTY
/*BEGIN_PROPERTY(Connection_Transaction)
CHECK_DB();
GB.ReturnInteger(THIS->trans);
END_PROPERTY*/
BEGIN_METHOD_VOID(Connection_Open)
CHECK_DB();
if (THIS->db.handle)
{
GB.Error("Connection already opened");
return;
}
open_connection(THIS);
END_METHOD
BEGIN_METHOD_VOID(Connection_Close)
CHECK_DB();
close_connection(THIS);
END_METHOD
#if 0
BEGIN_PROPERTY(CCONNECTION_ignore_case)
CHECK_DB();
CHECK_OPEN();
if (READ_PROPERTY)
GB.ReturnBoolean(THIS->db.ignore_case);
else
{
if (THIS->db.flags.no_case)
{
if (THIS->db.ignore_case)
GB.Error("This database driver cannot be case sensitive");
else
GB.Error("This database driver is always case sensitive");
return;
}
THIS->db.ignore_case = VPROP(GB_BOOLEAN);
}
END_PROPERTY
#endif
BEGIN_PROPERTY(Connection_IgnoreCharset)
CHECK_DB();
if (READ_PROPERTY)
GB.ReturnBoolean(THIS->ignore_charset);
else
THIS->ignore_charset = VPROP(GB_BOOLEAN);
END_PROPERTY
BEGIN_PROPERTY(Connection_Collations)
GB_ARRAY array;
CHECK_DB();
CHECK_OPEN();
if (!THIS->db.flags.no_collation)
{
array = THIS->driver->GetCollations(&THIS->db);
if (array)
{
GB.ReturnObject(array);
return;
}
}
GB.Error("Collations are not supported");
END_PROPERTY
BEGIN_METHOD_VOID(Connection_Begin)
CHECK_DB();
CHECK_OPEN();
if (!THIS->db.flags.no_nest || THIS->trans == 0)
THIS->driver->Begin(&THIS->db);
THIS->trans++;
END_METHOD
BEGIN_METHOD_VOID(Connection_Commit)
CHECK_DB();
CHECK_OPEN();
if (THIS->trans == 0)
{
//GB.Error("Not in a transaction");
return;
}
THIS->trans--;
if (!THIS->db.flags.no_nest || THIS->trans == 0)
THIS->driver->Commit(&THIS->db);
END_METHOD
BEGIN_METHOD_VOID(Connection_Rollback)
CHECK_DB();
CHECK_OPEN();
if (THIS->trans == 0)
{
//GB.Error("Not in a transaction");
return;
}
THIS->trans--;
if (!THIS->db.flags.no_nest || THIS->trans == 0)
THIS->driver->Rollback(&THIS->db);
END_METHOD
BEGIN_METHOD(Connection_Limit, GB_INTEGER limit)
CHECK_DB();
CHECK_OPEN();
THIS->limit = VARG(limit);
GB.ReturnObject(THIS);
END_PROPERTY
static char *_make_query_buffer;
static char *_make_query_original;
static void make_query_get_param(int index, char **str, int *len)
{
if (index == 1)
*str = _make_query_buffer;
else if (index == 2)
*str = _make_query_original;
*len = -1;
}
static char *make_query(CCONNECTION *_object, char *pattern, int len, int narg, GB_VALUE *arg)
{
char *query;
const char *keyword;
char buffer[32];
query = DB_MakeQuery(THIS->driver, &THIS->db, pattern, len, narg, arg);
if (query && THIS->limit > 0 && strncasecmp(query, "SELECT ", 7) == 0)
{
keyword = THIS->db.limit.keyword;
if (!keyword)
keyword = "LIMIT";
snprintf(buffer, sizeof(buffer), "%s %d", keyword, THIS->limit);
_make_query_buffer = buffer;
_make_query_original = &query[7];
switch (THIS->db.limit.pos)
{
case DB_LIMIT_AT_BEGIN:
query = GB.SubstString("SELECT &1 &2", 0, make_query_get_param);
break;
case DB_LIMIT_AT_END:
default:
query = GB.SubstString("SELECT &2 &1", 0, make_query_get_param);
break;
}
THIS->limit = 0;
}
return query;
}
BEGIN_METHOD(Connection_Exec, GB_STRING query; GB_VALUE param[0])
char *query;
CRESULT *result;
CHECK_DB();
CHECK_OPEN();
query = make_query(THIS, STRING(query), LENGTH(query), GB.NParam(), ARG(param[0]));
if (!query)
return;
result = DB_MakeResult(THIS, RESULT_FIND, NULL, query);
if (result)
GB.ReturnObject(result);
END_METHOD
BEGIN_METHOD(Connection_Create, GB_STRING table; GB_BOOLEAN ret)
CRESULT *result;
char *table = GB.ToZeroString(ARG(table));
CHECK_DB();
CHECK_OPEN();
if (!table || !*table)
{
GB.Error("Void table name");
return;
}
result = DB_MakeResult(THIS, RESULT_CREATE, table, NULL);
if (result)
{
if (THIS->db.flags.no_returning)
DB_Debug("gb.db", "'RETURNING' keyword is not supported by this '%s' connection", THIS->driver->name);
else
result->returning = VARGOPT(ret, FALSE);
GB.ReturnObject(result);
}
else
GB.ReturnNull();
END_METHOD
static char *get_query(char *prefix, CCONNECTION *_object, char *table, int len_table, char *query, int len_query, GB_VALUE *arg)
{
if (!len_table)
{
GB.Error("Void table name");
return NULL;
}
q_init();
q_add(prefix);
q_add(" ");
q_add(DB_GetQuotedTable(THIS->driver, &THIS->db, table, len_table));
if (query && len_query > 0)
{
q_add(" ");
if (strncasecmp(query, "WHERE ", 6) && strncasecmp(query, "ORDER BY ", 9))
q_add("WHERE ");
q_add_length(query, len_query);
}
query = make_query(THIS, q_get(), q_length(), GB.NParam(), arg);
return query;
}
BEGIN_METHOD(Connection_Find, GB_STRING table; GB_STRING query; GB_VALUE param[0])
char *query;
CRESULT *result;
CHECK_DB();
CHECK_OPEN();
query = get_query("SELECT * FROM", THIS, STRING(table), LENGTH(table),
MISSING(query) ? NULL : STRING(query),
MISSING(query) ? 0 : LENGTH(query),
ARG(param[0]));
if (!query)
return;
result = DB_MakeResult(THIS, RESULT_FIND, NULL, query);
if (result)
GB.ReturnObject(result);
END_METHOD
BEGIN_METHOD(Connection_Delete, GB_STRING table; GB_STRING query; GB_VALUE param[0])
char *query;
CHECK_DB();
CHECK_OPEN();
query = get_query("DELETE FROM", THIS, STRING(table), LENGTH(table),
MISSING(query) ? NULL : STRING(query),
MISSING(query) ? 0 : LENGTH(query),
ARG(param[0]));
if (!query)
return;
DB_MakeResult(THIS, RESULT_DELETE, NULL, query);
END_METHOD
BEGIN_METHOD(Connection_Edit, GB_STRING table; GB_STRING query; GB_VALUE param[0])
char *query;
CRESULT *result;
/*char *table = GB.ToZeroString(ARG(table));*/
CHECK_DB();
CHECK_OPEN();
/*if (check_table(THIS, table, TRUE))
return;*/
query = get_query("SELECT * FROM", THIS, STRING(table), LENGTH(table),
MISSING(query) ? NULL : STRING(query),
MISSING(query) ? 0 : LENGTH(query),
ARG(param[0]));
if (!query)
return;
result = DB_MakeResult(THIS, RESULT_EDIT, GB.ToZeroString(ARG(table)), query);
if (result)
GB.ReturnObject(result);
END_METHOD
BEGIN_METHOD(Connection_Quote, GB_STRING name; GB_BOOLEAN is_table)
char *name = STRING(name);
int len = LENGTH(name);
CHECK_DB();
CHECK_OPEN();
if (VARGOPT(is_table, FALSE)) // && THIS->db.flags.schema)
GB.ReturnNewZeroString(DB_GetQuotedTable(THIS->driver, &THIS->db, STRING(name), LENGTH(name)));
else
{
q_init();
q_add(THIS->driver->GetQuote());
q_add_length(name, len);
q_add(THIS->driver->GetQuote());
GB.ReturnString(q_get());
}
END_METHOD
BEGIN_METHOD(Connection_FormatBlob, GB_STRING data)
DB_BLOB blob;
CHECK_DB();
CHECK_OPEN();
blob.data = STRING(data);
blob.length = LENGTH(data);
q_init();
DB_CurrentDatabase = &THIS->db;
(*THIS->driver->FormatBlob)(&blob, q_add_length);
GB.ReturnString(q_get());
END_METHOD
BEGIN_METHOD(Connection_Subst, GB_STRING query; GB_VALUE param[0])
char *query;
CHECK_DB();
CHECK_OPEN();
query = make_query(THIS, STRING(query), LENGTH(query), GB.NParam(), ARG(param[0]));
if (!query)
return;
GB.ReturnString(query);
END_METHOD
BEGIN_PROPERTY(Connection_Current)
if (READ_PROPERTY)
GB.ReturnObject(_current);
else
_current = (CCONNECTION *)VPROP(GB_OBJECT);
END_PROPERTY
BEGIN_PROPERTY(Connection_Charset)
CHECK_DB();
CHECK_OPEN();
if (THIS->db.charset)
GB.ReturnString(THIS->db.charset);
else
GB.ReturnConstZeroString("ASCII");
END_PROPERTY
BEGIN_PROPERTY(Connection_Databases)
CHECK_DB();
CHECK_OPEN();
GB_SubCollectionNew(&THIS->databases, &_databases_desc, THIS);
GB.ReturnObject(THIS->databases);
END_PROPERTY
BEGIN_PROPERTY(Connection_Users)
CHECK_DB();
CHECK_OPEN();
GB_SubCollectionNew(&THIS->users, &_users_desc, THIS);
GB.ReturnObject(THIS->users);
END_PROPERTY
BEGIN_PROPERTY(Connection_Tables)
CHECK_DB();
CHECK_OPEN();
GB_SubCollectionNew(&THIS->tables, &_tables_desc, THIS);
GB.ReturnObject(THIS->tables);
END_PROPERTY
/*BEGIN_PROPERTY(CCONNECTION_views)
CHECK_DB();
CHECK_OPEN();
GB.SubCollection.New(&THIS->views, &_views_desc, THIS);
GB.ReturnObject(THIS->views);
END_PROPERTY*/
BEGIN_PROPERTY(Connection_Debug)
if (READ_PROPERTY)
GB.ReturnBoolean(DB_IsDebug());
else
DB_SetDebug(VPROP(GB_BOOLEAN));
END_PROPERTY
BEGIN_PROPERTY(Connection_Handle)
CHECK_DB();
GB.ReturnPointer(THIS->db.handle);
END_PROPERTY
BEGIN_PROPERTY(Connection_LastInsertId)
CHECK_DB();
CHECK_OPEN();
GB.ReturnLong((*THIS->driver->GetLastInsertId)(&THIS->db));
END_PROPERTY
GB_DESC CConnectionDesc[] =
{
GB_DECLARE("_Connection", sizeof(CCONNECTION)),
GB_METHOD("_new", NULL, Connection_new, NULL),
GB_METHOD("_free", NULL, Connection_free, NULL),
GB_PROPERTY("Type", "s", Connection_Type),
GB_PROPERTY("Host", "s", Connection_Host),
GB_PROPERTY("Login", "s", Connection_User),
GB_PROPERTY("User", "s", Connection_User),
GB_PROPERTY("Password", "s", Connection_Password),
GB_PROPERTY("Name", "s", Connection_Name),
GB_PROPERTY("Port", "s", Connection_Port),
GB_PROPERTY("Timeout", "i", Connection_Timeout),
//GB_PROPERTY("Timezone", "i", Connection_Timezone),
GB_PROPERTY_READ("Charset", "s", Connection_Charset),
GB_PROPERTY_READ("Version", "i", Connection_Version),
GB_PROPERTY_READ("FullVersion", "s", Connection_FullVersion),
GB_PROPERTY_READ("Opened", "b", Connection_Opened),
GB_PROPERTY_READ("Error", "i", Connection_Error),
//GB_PROPERTY_READ("Transaction", "i", Connection_Transaction),
GB_PROPERTY("IgnoreCharset", "b", Connection_IgnoreCharset),
GB_PROPERTY_READ("Collations", "String[]", Connection_Collations),
GB_PROPERTY_READ("Handle", "p", Connection_Handle),
GB_PROPERTY_READ("LastInsertId", "l", Connection_LastInsertId),
GB_METHOD("Open", NULL, Connection_Open, NULL),
GB_METHOD("Close", NULL, Connection_Close, NULL),
GB_METHOD("Limit", "Connection", Connection_Limit, "(Limit)i"),
GB_METHOD("Exec", "Result", Connection_Exec, "(Request)s(Arguments)."),
GB_METHOD("Create", "Result", Connection_Create, "(Table)s[(Return)b]"),
GB_METHOD("Find", "Result", Connection_Find, "(Table)s[(Request)s(Arguments).]"),
GB_METHOD("Edit", "Result", Connection_Edit, "(Table)s[(Request)s(Arguments).]"),
GB_METHOD("Delete", NULL, Connection_Delete, "(Table)s[(Request)s(Arguments).]"),
GB_METHOD("Subst", "s", Connection_Subst, "(Format)s(Arguments)."),
GB_METHOD("Begin", NULL, Connection_Begin, NULL),
GB_METHOD("Commit", NULL, Connection_Commit, NULL),
GB_METHOD("Rollback", NULL, Connection_Rollback, NULL),
GB_METHOD("Quote", "s", Connection_Quote, "(Name)s[(Table)b]"),
GB_METHOD("FormatBlob", "s", Connection_FormatBlob, "(Data)s"),
GB_PROPERTY("Tables", ".Connection.Tables", Connection_Tables),
GB_PROPERTY("Databases", ".Connection.Databases", Connection_Databases),
GB_PROPERTY("Users", ".Connection.Users", Connection_Users),
//GB_PROPERTY("Views", ".ConnectionViews", CCONNECTION_views),
GB_CONSTANT("_Properties", "s", "Type,Host,Login,Password,Name,Port"),
GB_END_DECLARE
};
GB_DESC CDBDesc[] =
{
GB_DECLARE("DB", 0), GB_VIRTUAL_CLASS(),
GB_CONSTANT("Boolean", "i", GB_T_BOOLEAN),
GB_CONSTANT("Integer", "i", GB_T_INTEGER),
GB_CONSTANT("Long", "i", GB_T_LONG),
GB_CONSTANT("Float", "i", GB_T_FLOAT),
GB_CONSTANT("Date", "i", GB_T_DATE),
GB_CONSTANT("String", "i", GB_T_STRING),
GB_CONSTANT("Serial", "i", DB_T_SERIAL),
GB_CONSTANT("Blob", "i", DB_T_BLOB),
GB_STATIC_PROPERTY("Current", "Connection", Connection_Current),
GB_STATIC_METHOD("Open", NULL, Connection_Open, NULL),
GB_STATIC_METHOD("Close", NULL, Connection_Close, NULL),
GB_STATIC_PROPERTY_READ("Charset", "s", Connection_Charset),
GB_STATIC_PROPERTY_READ("Version", "i", Connection_Version),
GB_STATIC_PROPERTY_READ("FullVersion", "s", Connection_FullVersion),
GB_STATIC_PROPERTY_READ("Opened", "b", Connection_Opened),
GB_STATIC_PROPERTY_READ("Error", "i", Connection_Error),
//GB_STATIC_PROPERTY_READ("Transaction", "i", Connection_Transaction),
GB_STATIC_PROPERTY("IgnoreCharset", "b", Connection_IgnoreCharset),
GB_STATIC_PROPERTY_READ("Collations", "String[]", Connection_Collations),
GB_STATIC_PROPERTY_READ("Handle", "p", Connection_Handle),
GB_STATIC_PROPERTY_READ("LastInsertId", "l", Connection_LastInsertId),
GB_STATIC_PROPERTY("Debug", "b", Connection_Debug),
GB_STATIC_METHOD("Limit", "Connection", Connection_Limit, "(Limit)i"),
GB_STATIC_METHOD("Exec", "Result", Connection_Exec, "(Request)s(Arguments)."),
GB_STATIC_METHOD("Create", "Result", Connection_Create, "(Table)s[(Return)b]"),
GB_STATIC_METHOD("Find", "Result", Connection_Find, "(Table)s[(Request)s(Arguments).]"),
GB_STATIC_METHOD("Edit", "Result", Connection_Edit, "(Table)s[(Request)s(Arguments).]"),
GB_STATIC_METHOD("Delete", NULL, Connection_Delete, "(Table)s[(Request)s(Arguments).]"),
GB_STATIC_METHOD("Subst", "s", Connection_Subst, "(Format)s(Arguments)."),
GB_STATIC_METHOD("Begin", NULL, Connection_Begin, NULL),
GB_STATIC_METHOD("Commit", NULL, Connection_Commit, NULL),
GB_STATIC_METHOD("Rollback", NULL, Connection_Rollback, NULL),
GB_STATIC_METHOD("Quote", "s", Connection_Quote, "(Name)s[(Table)b]"),
GB_STATIC_METHOD("FormatBlob", "s", Connection_FormatBlob, "(Data)s"),
GB_STATIC_PROPERTY("Tables", ".Connection.Tables", Connection_Tables),
//GB_STATIC_PROPERTY("Views", ".ConnectionViews", CCONNECTION_views),
GB_STATIC_PROPERTY("Databases", ".Connection.Databases", Connection_Databases),
GB_STATIC_PROPERTY("Users", ".Connection.Users", Connection_Users),
GB_END_DECLARE
};