gambas-source-code/main/lib/db/CResult.c
gambas e07ac2e4f2 Connection.LastInsertId is a new property that returns the value of the serial field used in the last inserted row.
[GB.DB]
* NEW: Connection.LastInsertId is a new property that returns the value of the serial field used in the last inserted row. It is supported on MySQL, PostgreSQL and SQLite3 only.
* BUG: Connection.Handle is correctly declared now.

[GB.DB.MYSQL]
* NEW: Support for Connection.LastInsertId.

[GB.DB.ODBC]
* NEW: Raise an error if Connection.LastInsertId is used.

[GB.DB.POSTGRESQL]
* NEW: Support for Connection.LastInsertId.

[GB.DB.SQLITE2]
* NEW: Raise an error if Connection.LastInsertId is used.

[GB.DB.SQLITE3]
* NEW: Support for Connection.LastInsertId.
2018-03-20 16:05:33 +01:00

996 lines
19 KiB
C

/***************************************************************************
CResult.c
(c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>
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 __CRESULT_C
#include "main.h"
#include "deletemap.h"
#include "CField.h"
#include "CResultField.h"
#include "CResult.h"
GB_CLASS CLASS_Blob;
static CBLOB *make_blob(CRESULT *result, int field);
static void set_blob(CBLOB *_object, char *data, int length);
static SUBCOLLECTION_DESC _fields_desc =
{
".Result.Fields",
(void *)CRESULTFIELD_get,
(void *)CRESULTFIELD_exist,
(void *)NULL,
(void *)CRESULTFIELD_release
};
static int check_result(CRESULT *_object)
{
return (THIS->conn->db.handle == NULL);
}
static bool check_available(CRESULT *_object)
{
if (!THIS->available)
{
GB.Error("Result is not available");
return TRUE;
}
else
return FALSE;
}
static void init_buffer(CRESULT *_object)
{
int i;
if (THIS->info.nfield == 0)
return;
GB.Alloc(POINTER(&THIS->buffer), sizeof(GB_VARIANT_VALUE) * THIS->info.nfield);
BARRAY_create(&THIS->changed, THIS->info.nfield);
BARRAY_clear_all(THIS->changed, THIS->info.nfield);
for (i = 0; i < THIS->info.nfield; i++)
THIS->buffer[i].type = GB_T_NULL;
}
static void void_buffer(CRESULT *_object)
{
int i;
//fprintf(stderr, "void_buffer\n");
if (THIS->info.nfield == 0)
return;
for (i = 0; i < THIS->info.nfield; i++)
GB.StoreVariant(NULL, &THIS->buffer[i]);
BARRAY_clear_all(THIS->changed, THIS->info.nfield);
}
static void release_buffer(CRESULT *_object)
{
if (THIS->buffer)
{
void_buffer(THIS);
GB.Free(POINTER(&THIS->buffer));
BARRAY_delete(&THIS->changed);
}
}
static bool load_buffer(CRESULT *_object, int vpos)
{
int i, ind;
int pos;
int result;
if (vpos == THIS->pos)
return FALSE;
DB_CurrentDatabase = &THIS->conn->db;
if (THIS->count < 0)
{
if (vpos != (THIS->pos + 1))
{
GB.Error("Result is forward only");
return TRUE;
}
}
else
{
if (vpos < 0 || vpos >= THIS->count || THIS->info.nfield == 0)
{
THIS->pos = -1;
THIS->available = FALSE;
return TRUE;
}
}
pos = DELETE_MAP_virtual_to_real(THIS->dmap, vpos);
//fprintf(stderr, "Result %p: Loading real %ld\n", THIS, pos);
void_buffer(THIS);
if (THIS->handle)
{
result = THIS->driver->Result.Fill(&THIS->conn->db, THIS->handle, pos, THIS->buffer, (pos > 0) && (pos == (DELETE_MAP_virtual_to_real(THIS->dmap, THIS->pos) + 1)));
if (result == DB_ERROR)
return TRUE;
else if (result == DB_NO_DATA)
{
THIS->pos = -1;
THIS->available = FALSE;
return TRUE;
}
if (THIS->mode == RESULT_EDIT)
{
q_init();
for (i = 0; i < THIS->info.nindex; i++)
{
ind = THIS->info.index[i];
if (i > 0) q_add(" AND ");
q_add(THIS->info.field[ind].name);
if (THIS->buffer[ind].type == GB_T_NULL)
q_add(" IS NULL");
else
{
q_add(" = ");
DB_FormatVariant(THIS->driver, &THIS->buffer[ind], q_add_length);
}
}
GB.FreeString(&THIS->edit);
THIS->edit = q_steal();
}
}
THIS->pos = vpos;
THIS->available = TRUE;
return FALSE;
}
static void reload_buffer(CRESULT *_object)
{
int pos = THIS->pos;
THIS->pos = -1;
load_buffer(THIS, pos);
}
static void table_release(DB_INFO *info)
{
int i;
if (info->table)
GB.FreeString(&info->table);
if (info->field)
{
for (i = 0; i < info->nfield; i++)
CFIELD_free_info(&info->field[i]);
GB.Free(POINTER(&info->field));
}
if (info->index)
GB.Free(POINTER(&info->index));
}
static GB_TYPE get_field_type(CRESULT *_object, int field)
{
GB_TYPE type;
if (THIS->info.field)
type = THIS->info.field[field].type;
else
type = THIS->driver->Result.Field.Type(THIS->handle, field);
//fprintf(stderr, "get_field_type: %d -> %ld\n", field, type);
return type;
}
CRESULT *DB_MakeResult(CCONNECTION *conn, int mode, char *table_temp, char *query)
{
CRESULT *_object;
DB_RESULT res;
char *duplicate;
char *token;
const char *error = NULL;
char *arg;
char *table;
switch (mode)
{
case RESULT_FIND:
if (conn->driver->Exec(&conn->db, query, &res, "Query failed: &1"))
return NULL;
break;
case RESULT_CREATE:
res = NULL;
break;
case RESULT_EDIT:
if (conn->driver->Exec(&conn->db, query, &res, "Query failed: &1"))
return NULL;
break;
case RESULT_DELETE:
conn->driver->Exec(&conn->db, query, NULL, "Query failed: &1");
return NULL;
}
_object = GB.New(GB.FindClass("Result"), NULL, NULL);
THIS->conn = conn;
GB.Ref(conn);
THIS->driver = conn->driver;
THIS->available = FALSE;
THIS->mode = mode;
THIS->handle = res;
THIS->pos = -1;
THIS->dmap = NULL;
// table must be copied because it can be a temporary string!
table = GB.NewZeroString(table_temp);
switch (mode)
{
case RESULT_FIND:
THIS->driver->Result.Init(THIS->handle, &THIS->info, &THIS->count);
break;
case RESULT_CREATE:
if (THIS->driver->Table.Init(&conn->db, table, &THIS->info))
goto ERROR;
THIS->count = 1;
break;
case RESULT_EDIT:
THIS->driver->Result.Init(THIS->handle, &THIS->info, &THIS->count);
if (THIS->driver->Table.Init(&conn->db, table, &THIS->info))
goto ERROR;
if (THIS->driver->Table.Index(&conn->db, table, &THIS->info))
{
error = "Table '&1' has no primary key";
arg = table;
goto ERROR;
}
break;
}
init_buffer(THIS);
load_buffer(THIS, 0);
GB.FreeString(&table);
return THIS;
ERROR:
if (!error)
{
if (strchr(table, (int)',') == NULL)
{
arg = table;
if (!THIS->driver->Table.Exist(&conn->db, table))
error = "Unknown table: &1";
else
error = "Cannot read information about table &1";
}
else
{
duplicate = GB.NewZeroString(table);
token = strtok(duplicate,",");
do {
arg = token;
if (!THIS->driver->Table.Exist(&conn->db, token))
error = "Unknown table: &1";
else
error = "Cannot read information about table '&1'";
}
while ((token = strtok(NULL, ".")) != NULL);
GB.FreeString(&duplicate);
}
}
GB.Free(POINTER(&_object));
GB.Error(error, arg);
GB.FreeString(&table);
return NULL;
}
BEGIN_METHOD_VOID(Result_free)
release_buffer(THIS);
if (THIS->mode != RESULT_CREATE)
THIS->driver->Result.Release(THIS->handle, &THIS->info);
if (THIS->mode != RESULT_FIND)
table_release(&THIS->info);
if (THIS->edit)
GB.FreeString(&THIS->edit);
DELETE_MAP_free(&THIS->dmap);
GB.Unref(POINTER(&THIS->conn));
GB.Unref(POINTER(&THIS->fields));
END_METHOD
BEGIN_PROPERTY(Result_Count)
GB.ReturnInteger(THIS->count);
END_PROPERTY
BEGIN_PROPERTY(Result_Max)
GB.ReturnInteger(THIS->count - 1);
END_PROPERTY
BEGIN_PROPERTY(Result_Index)
GB.ReturnInteger(THIS->pos);
END_PROPERTY
BEGIN_PROPERTY(Result_Available)
GB.ReturnBoolean(THIS->available);
END_PROPERTY
static void check_blob(CRESULT *_object, int field)
{
GB_VARIANT val;
//fprintf(stderr, "check_blob: %d\n", field);
if (THIS->buffer[field].type == GB_T_NULL)
{
val.type = GB_T_VARIANT;
val.value.type = (GB_TYPE)CLASS_Blob;
val.value.value._object = make_blob(THIS, field);
GB.StoreVariant(&val, &THIS->buffer[field]);
}
}
BEGIN_METHOD(Result_get, GB_STRING field)
int index;
GB_TYPE type;
if (check_available(THIS))
return;
index = CRESULTFIELD_find(THIS, GB.ToZeroString(ARG(field)), TRUE);
if (index < 0)
return;
type = get_field_type(THIS, index);
if (type == DB_T_BLOB)
check_blob(THIS, index);
GB.ReturnVariant(&THIS->buffer[index]);
END_METHOD
BEGIN_METHOD(Result_GetAll, GB_STRING field)
int index;
int pos;
GB_TYPE type, atype;
GB_ARRAY result;
GB_VARIANT_VALUE *val;
index = CRESULTFIELD_find(THIS, GB.ToZeroString(ARG(field)), TRUE);
if (index < 0)
return;
atype = type = get_field_type(THIS, index);
if (atype == DB_T_SERIAL)
atype = GB_T_LONG;
else if (atype == DB_T_BLOB)
atype = GB_T_OBJECT;
GB.Array.New(POINTER(&result), atype, 0);
pos = THIS->pos;
load_buffer(THIS, 0);
while (THIS->available)
{
if (type == DB_T_BLOB)
check_blob(THIS, index);
val = &THIS->buffer[index];
switch (atype)
{
case GB_T_BOOLEAN: *(char *)GB.Array.Add(result) = val->value._boolean; break;
case GB_T_INTEGER: *(int *)GB.Array.Add(result) = val->value._integer; break;
case GB_T_LONG: *(int64_t *)GB.Array.Add(result) = val->value._long; break;
case GB_T_FLOAT: *(double *)GB.Array.Add(result) = val->value._float; break;
case GB_T_DATE: *(GB_DATE_VALUE *)GB.Array.Add(result) = val->value._date; break;
case GB_T_STRING:
if (val->type == GB_T_CSTRING)
*(char **)GB.Array.Add(result) = GB.NewString(val->value._string, strlen(val->value._string));
else
*(char **)GB.Array.Add(result) = GB.RefString(val->value._string);
break;
case GB_T_OBJECT: *(void **)GB.Array.Add(result) = val->value._object; GB.Ref(val->value._object); break;
}
load_buffer(THIS, THIS->pos + 1);
}
if (THIS->count >= 0)
load_buffer(THIS, pos);
GB.ReturnObject(result);
END_METHOD
BEGIN_METHOD(Result_put, GB_VARIANT value; GB_STRING field)
int index;
GB_TYPE type;
if (check_available(THIS))
return;
if (THIS->mode == RESULT_FIND)
{
GB.Error("Result is read-only");
return;
}
index = CRESULTFIELD_find(THIS, GB.ToZeroString(ARG(field)), TRUE);
if (index < 0)
return;
type = get_field_type(THIS, index);
if (type == DB_T_SERIAL)
{
//GB.Error("Read-only field");
return;
}
if (type == DB_T_BLOB)
{
check_blob(THIS, index);
if (VARG(value).type == (GB_TYPE)CLASS_Blob)
{
CBLOB *src = VARG(value).value._object;
set_blob((CBLOB *)THIS->buffer[index].value._object, src->data, src->length);
}
else
{
GB_STRING *str = (GB_STRING *)(void *)ARG(value);
if (GB.Conv((GB_VALUE *)(void *)ARG(value), GB_T_STRING))
return;
set_blob((CBLOB *)THIS->buffer[index].value._object, str->value.addr + str->value.start, str->value.len);
}
BARRAY_set(THIS->changed, index);
return;
}
if (VARG(value).type != GB_T_NULL && VARG(value).type != type)
{
if (GB.Conv((GB_VALUE *)(void *)ARG(value), THIS->info.field[index].type))
{
GB.Error("Type mismatch");
return;
}
GB.Conv((GB_VALUE *)(void *)ARG(value), GB_T_VARIANT);
}
GB.StoreVariant(ARG(value), &THIS->buffer[index]);
BARRAY_set(THIS->changed, index);
END_METHOD
#if 0
BEGIN_METHOD(CRESULT_copy, GB_OBJECT result)
CRESULT *result = (CRESULT *)VARG(result);
int index;
if (THIS->mode == RESULT_FIND)
{
GB.Error("Result is read-only");
return;
}
for (index = 0; index <
index = find_field(THIS, GB.ToZeroString(ARG(field)));
if (index < 0)
return;
if (VARG(value).type != GB_T_NULL && VARG(value).type != THIS->info.types[index])
/*{
GB.Error("Type mismatch");
return;
}*/
{
if (GB.Conv((GB_VALUE *)ARG(value), THIS->info.types[index]))
return;
GB.Conv((GB_VALUE *)ARG(value), GB_T_VARIANT);
}
GB.StoreVariant(ARG(value), &THIS->buffer[index]);
END_METHOD
#endif
BEGIN_METHOD_VOID(Result_MoveFirst)
GB.ReturnBoolean(load_buffer(THIS, 0));
END_METHOD
BEGIN_METHOD_VOID(Result_MoveLast)
if (THIS->count < 0)
GB.Error("Result is forward only");
else
GB.ReturnBoolean(load_buffer(THIS, THIS->count - 1));
END_METHOD
BEGIN_METHOD_VOID(Result_MovePrevious)
GB.ReturnBoolean(load_buffer(THIS, THIS->pos - 1));
END_METHOD
BEGIN_METHOD_VOID(Result_MoveNext)
GB.ReturnBoolean(load_buffer(THIS, THIS->pos + 1));
END_METHOD
BEGIN_METHOD(Result_MoveTo, GB_INTEGER pos)
GB.ReturnBoolean(load_buffer(THIS, VARG(pos)));
END_METHOD
BEGIN_METHOD_VOID(Result_next)
int *pos = (int *)GB.GetEnum();
if (!load_buffer(THIS, *pos))
(*pos)++;
else
GB.StopEnum();
END_METHOD
BEGIN_METHOD_VOID(Result_Update)
int i;
bool comma;
DB_INFO *info = &THIS->info;
if (check_available(THIS))
return;
DB_CurrentDatabase = &THIS->conn->db;
q_init();
switch(THIS->mode)
{
case RESULT_CREATE:
if (BARRAY_is_void(THIS->changed, THIS->info.nfield))
break;
q_add("INSERT INTO ");
q_add(DB_GetQuotedTable(THIS->driver, DB_CurrentDatabase, info->table));
q_add(" ( ");
comma = FALSE;
for (i = 0; i < info->nfield; i++)
{
if (THIS->buffer[i].type == GB_T_NULL)
continue;
if (!BARRAY_test(THIS->changed, i))
continue;
if (comma) q_add(", ");
q_add(THIS->driver->GetQuote());
q_add(info->field[i].name);
q_add(THIS->driver->GetQuote());
comma = TRUE;
}
if (!comma)
{
q_add(THIS->driver->GetQuote());
q_add(info->field[0].name);
q_add(THIS->driver->GetQuote());
}
q_add(" ) VALUES ( ");
comma = FALSE;
for (i = 0; i < info->nfield; i++)
{
if (THIS->buffer[i].type == GB_T_NULL)
continue;
if (!BARRAY_test(THIS->changed, i))
continue;
if (comma) q_add(", ");
DB_FormatVariant(THIS->driver, &THIS->buffer[i], q_add_length);
comma = TRUE;
}
if (!comma)
{
DB_FormatVariant(THIS->driver, &THIS->buffer[0], q_add_length);
}
q_add(" )");
if (!THIS->driver->Exec(&THIS->conn->db, q_get(), NULL, "Cannot create record: &1"))
void_buffer(THIS);
break;
case RESULT_EDIT:
if (BARRAY_is_void(THIS->changed, THIS->info.nfield))
break;
q_add("UPDATE ");
q_add(DB_GetQuotedTable(THIS->driver, DB_CurrentDatabase, info->table));
q_add(" SET ");
comma = FALSE;
for (i = 0; i < info->nfield; i++)
{
if (!BARRAY_test(THIS->changed, i))
continue;
if (comma) q_add(", ");
q_add(THIS->driver->GetQuote());
q_add(THIS->info.field[i].name);
q_add(THIS->driver->GetQuote());
q_add(" = ");
DB_FormatVariant(THIS->driver, &THIS->buffer[i], q_add_length);
comma = TRUE;
}
q_add(" WHERE ");
q_add(THIS->edit);
THIS->driver->Exec(&THIS->conn->db, q_get(), NULL, "Cannot modify record: &1");
break;
default:
GB.Error("Result is read-only");
break;
}
BARRAY_clear_all(THIS->changed, THIS->info.nfield);
END_METHOD
BEGIN_METHOD(Result_Delete, GB_BOOLEAN keep)
DB_INFO *info = &THIS->info;
int *pos;
void *save_enum;
if (check_available(THIS))
return;
q_init();
switch(THIS->mode)
{
case RESULT_CREATE:
void_buffer(THIS);
break;
case RESULT_EDIT:
q_add("DELETE FROM ");
q_add(DB_GetQuotedTable(THIS->driver, DB_CurrentDatabase, info->table));
q_add(" WHERE ");
q_add(THIS->edit);
THIS->driver->Exec(&THIS->conn->db, q_get(), NULL, "Cannot delete record: &1");
if (!VARGOPT(keep, FALSE))
{
DELETE_MAP_add(&THIS->dmap, THIS->pos);
THIS->count--;
reload_buffer(THIS);
save_enum = GB.BeginEnum(THIS);
while (!GB.NextEnum())
{
pos = (int *)GB.GetEnum();
if (*pos > THIS->pos)
(*pos)--;
}
GB.EndEnum(save_enum);
}
break;
default:
GB.Error("Result is read-only");
break;
}
END_METHOD
BEGIN_PROPERTY(Result_Fields)
GB_SubCollectionNew(&THIS->fields, &_fields_desc, THIS);
GB.ReturnObject(THIS->fields);
END_PROPERTY
BEGIN_PROPERTY(Result_Connection)
GB.ReturnObject(THIS->conn);
END_PROPERTY
GB_DESC CResultDesc[] =
{
GB_DECLARE("Result", sizeof(CRESULT)), GB_NOT_CREATABLE(),
GB_HOOK_CHECK(check_result),
GB_METHOD("_free", NULL, Result_free, NULL),
GB_PROPERTY_READ("Count", "i", Result_Count),
GB_PROPERTY_READ("Length", "i", Result_Count),
GB_PROPERTY_READ("Available", "b", Result_Available),
GB_PROPERTY_READ("Index", "i", Result_Index),
GB_PROPERTY_READ("Max", "i", Result_Max),
GB_METHOD("_get", "v", Result_get, "(Field)s"),
GB_METHOD("_put", NULL, Result_put, "(Value)v(Field)s"),
GB_METHOD("_next", NULL, Result_next, NULL),
GB_METHOD("MoveFirst", "b", Result_MoveFirst, NULL),
GB_METHOD("MoveLast", "b", Result_MoveLast, NULL),
GB_METHOD("MovePrevious", "b", Result_MovePrevious, NULL),
GB_METHOD("MoveNext", "b", Result_MoveNext, NULL),
GB_METHOD("MoveTo", "b", Result_MoveTo, "(Index)i"),
GB_METHOD("Update", NULL, Result_Update, NULL),
GB_METHOD("Delete", NULL, Result_Delete, "[(Keep)b]"),
GB_METHOD("All", "Array", Result_GetAll, "(Field)s"),
GB_PROPERTY_READ("Fields", ".Result.Fields", Result_Fields),
GB_PROPERTY_READ("Connection", "Connection", Result_Connection),
GB_END_DECLARE
};
/** Blob *******************************************************************/
static bool _convert_blob(CBLOB *_object, GB_TYPE type, GB_VALUE *conv)
{
if (BLOB)
{
switch (type)
{
case GB_T_STRING:
case GB_T_CSTRING:
conv->_string.value.addr = BLOB->data;
conv->_string.value.start = 0;
conv->_string.value.len = BLOB->length;
conv->type = GB_T_CSTRING;
return FALSE;
default:
return TRUE;
}
}
else
return TRUE;
}
/*static int check_blob(CBLOB *_object)
{
return check_result(BLOB->result) || (BLOB->result->pos != BLOB->pos);
}*/
static CBLOB *make_blob(CRESULT *result, int field)
{
CBLOB *_object;
_object = GB.New(CLASS_Blob, NULL, NULL);
//BLOB->result = result;
//GB.Ref(result);
//BLOB->field = field;
//BLOB->pos = result->pos;
BLOB->data = NULL;
BLOB->length = 0;
BLOB->constant = TRUE;
if (result->handle && result->pos >= 0)
{
BLOB->constant = FALSE;
result->driver->Result.Blob(result->handle, result->pos, field, BLOB);
if (BLOB->constant)
set_blob(BLOB, BLOB->data, BLOB->length);
}
//fprintf(stderr, "make_blob: [%d] %d (%d) -> %p\n", result->pos, field, BLOB->length, BLOB);
//GB.UnrefKeep(POINTER(&_object), FALSE);
return BLOB;
}
static void set_blob(CBLOB *_object, char *data, int length)
{
if (!BLOB->constant && BLOB->data)
GB.FreeString((char **)&BLOB->data);
if (data && length)
{
BLOB->data = GB.NewString(data, length);
BLOB->constant = FALSE;
}
BLOB->length = length;
}
BEGIN_METHOD_VOID(Blob_init)
CLASS_Blob = GB.FindClass("Blob");
END_METHOD
BEGIN_METHOD_VOID(Blob_free)
//GB.Unref(POINTER(&BLOB->result));
set_blob(BLOB, NULL, 0);
END_METHOD
/*BEGIN_PROPERTY(CBLOB_result)
GB.ReturnObject(BLOB->result);
END_PROPERTY*/
BEGIN_PROPERTY(Blob_Data)
if (READ_PROPERTY)
{
if (BLOB->length)
GB.ReturnConstString(BLOB->data, BLOB->length);
else
GB.ReturnVoidString();
}
else
{
set_blob(BLOB, PSTRING(), PLENGTH());
}
END_PROPERTY
BEGIN_PROPERTY(Blob_Length)
GB.ReturnInteger(BLOB->length);
END_PROPERTY
GB_DESC CBlobDesc[] =
{
GB_DECLARE("Blob", sizeof(CBLOB)), GB_NOT_CREATABLE(),
//GB_HOOK_CHECK(check_blob),
GB_STATIC_METHOD("_init", NULL, Blob_init, NULL),
GB_METHOD("_free", NULL, Blob_free, NULL),
//GB_PROPERTY_READ("Result", "Result", CBLOB_result),
GB_PROPERTY("Data", "s", Blob_Data),
GB_PROPERTY_READ("Length", "i", Blob_Length),
GB_INTERFACE("_convert", &_convert_blob),
//GB_METHOD("_unknown", "v", CBLOB_unknown, "v"),
GB_END_DECLARE
};