From 2f4e42ab4639a8d3deeefcfbe5db146b3ee42ad0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= Date: Fri, 25 May 2007 14:42:50 +0000 Subject: [PATCH] Initial import - moving from lb-utils.db. --- org.glite.lbjp-utils.db/.cvsignore | 3 + org.glite.lbjp-utils.db/LICENSE | 69 ++ org.glite.lbjp-utils.db/Makefile | 137 ++++ org.glite.lbjp-utils.db/examples/db_expire.c | 115 +++ org.glite.lbjp-utils.db/examples/db_test.c | 196 +++++ org.glite.lbjp-utils.db/interface/db.h | 344 +++++++++ org.glite.lbjp-utils.db/src/db.c | 1039 ++++++++++++++++++++++++++ 7 files changed, 1903 insertions(+) create mode 100644 org.glite.lbjp-utils.db/.cvsignore create mode 100644 org.glite.lbjp-utils.db/LICENSE create mode 100644 org.glite.lbjp-utils.db/Makefile create mode 100644 org.glite.lbjp-utils.db/examples/db_expire.c create mode 100644 org.glite.lbjp-utils.db/examples/db_test.c create mode 100644 org.glite.lbjp-utils.db/interface/db.h create mode 100644 org.glite.lbjp-utils.db/src/db.c diff --git a/org.glite.lbjp-utils.db/.cvsignore b/org.glite.lbjp-utils.db/.cvsignore new file mode 100644 index 0000000..b7d6c92 --- /dev/null +++ b/org.glite.lbjp-utils.db/.cvsignore @@ -0,0 +1,3 @@ +build +doc +reports diff --git a/org.glite.lbjp-utils.db/LICENSE b/org.glite.lbjp-utils.db/LICENSE new file mode 100644 index 0000000..01b973b --- /dev/null +++ b/org.glite.lbjp-utils.db/LICENSE @@ -0,0 +1,69 @@ +LICENSE file for EGEE Middleware +================================ + +Copyright (c) 2004 on behalf of the EU EGEE Project: +The European Organization for Nuclear Research (CERN), +Istituto Nazionale di Fisica Nucleare (INFN), Italy +Datamat Spa, Italy +Centre National de la Recherche Scientifique (CNRS), France +CS Systeme d'Information (CSSI), France +Royal Institute of Technology, Center for Parallel Computers (KTH-PDC), Sweden +Universiteit van Amsterdam (UvA), Netherlands +University of Helsinki (UH.HIP), Finlan +University of Bergen (UiB), Norway +Council for the Central Laboratory of the Research Councils (CCLRC), United Kingdom + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. The end-user documentation included with the redistribution, if +any, must include the following acknowledgment: "This product includes +software developed by The EU EGEE Project (http://cern.ch/eu-egee/)." +Alternatively, this acknowledgment may appear in the software itself, if +and wherever such third-party acknowledgments normally appear. + +4. The names EGEE and the EU EGEE Project must not be +used to endorse or promote products derived from this software without +prior written permission. For written permission, please contact +. + +5. You are under no obligation whatsoever to provide anyone with any +bug fixes, patches, or upgrades to the features, functionality or +performance of the Software ("Enhancements") that you may develop over +time; however, if you choose to provide your Enhancements to The EU +EGEE Project, or if you choose to otherwise publish or distribute your +Enhancements, in source code form without contemporaneously requiring +end users of The EU EGEE Proejct to enter into a separate written license +agreement for such Enhancements, then you hereby grant The EU EGEE Project +a non-exclusive, royalty-free perpetual license to install, use, copy, +modify, prepare derivative works, incorporate into the EGEE Middleware +or any other computer software, distribute, and sublicense your +Enhancements or derivative works thereof, in binary and source code +form (if any), whether developed by The EU EGEE Project or third parties. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL PROJECT OR ITS CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +This software consists of voluntary contributions made by many +individuals on behalf of the EU EGEE Prject. For more information on The +EU EGEE Project, please see http://cern.ch/eu-egee/. For more information on +EGEE Middleware, please see http://egee-jra1.web.cern.ch/egee-jra1/ + + diff --git a/org.glite.lbjp-utils.db/Makefile b/org.glite.lbjp-utils.db/Makefile new file mode 100644 index 0000000..447dd4c --- /dev/null +++ b/org.glite.lbjp-utils.db/Makefile @@ -0,0 +1,137 @@ +# defaults +top_srcdir=. +builddir=build +stagedir=. +distdir=. +globalprefix=glite +lbutilsprefix=lb-utils +package=glite-lb-utils-db +version=0.2.0 +PREFIX=/opt/glite +flavour=gcc32thr + +glite_location=/opt/glite +mysql_prefix=/opt/mysql +mysql_version=4.1.11 +cppunit_prefix=/opt/cppunit +thrflavour=gcc32pthr +nothrflavour=gcc32 + +-include Makefile.inc +-include ../project/version.properties + +version=${module.version} + +CC=gcc + +VPATH=${top_srcdir}/interface:${top_srcdir}/src:${top_srcdir}/examples + +DEBUG:=-g -O0 -Wall + +CFLAGS:= \ + ${DEBUG} \ + -DVERSION=\"${version}\" \ + -I${stagedir}/include -I${top_srcdir}/src -I. \ + -I${top_srcdir}/interface \ + ${COVERAGE_FLAGS} \ + -I${mysql_prefix}/include -I${mysql_prefix}/include/mysql \ + -D_GNU_SOURCE + +ifdef LBS_DB_PROFILE + CFLAGS:=${CFLAGS} -DLBS_DB_PROFILE +endif + +TEST_LIBS:=-L${cppunit_prefix}/lib -lcppunit +TEST_INC:=-I${cppunit_prefix}/include + +LDFLAGS:=-L${stagedir}/lib ${COVERAGE_FLAGS} + +COMPILE:=libtool --mode=compile ${CC} ${CFLAGS} +LINK:=libtool --mode=link ${CC} -rpath ${stagedir}/lib ${LDFLAGS} +INSTALL:=libtool --mode=install install + +archlib:=lib +host_cpu:=${shell uname -m} +ifeq (${host_cpu},x86_64) + archlib:=lib64 +endif + +ifneq (${mysql_prefix},/usr) + ifeq ($(shell echo ${mysql_version} | cut -d. -f1,2),4.1) + mysqlib := -L${mysql_prefix}/${archlib}/mysql + else + mysqlib := -L${mysql_prefix}/${archlib} + endif +endif + +EXT_LIBS:=${mysqlib} -lmysqlclient -lz -lglite_lbu_trio +OBJS:=db.o +TESTOBJS:=dbtest.o +HDRS:=db.h +LOBJS:=${OBJS:.o=.lo} +LTESTOBJS:=${TESTOBJS:.o=.lo} + +libglite_lbu_db.la: ${LOBJS} + ${LINK} -o $@ $< ${EXT_LIBS} + +libglite_lbu_dbtest.la: ${LTESTOBJS} + ${LINK} -o $@ $< ${EXT_LIBS} + +dbtest.lo dbtest.o: db.c db.h + ${COMPILE} -DGLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH=10 -c $< -o $@ + +db_test: db_test.lo libglite_lbu_dbtest.la + ${LINK} -o $@ $+ ${EXT_LIBS} + +db_expire: db_expire.lo libglite_lbu_dbtest.la + ${LINK} -o $@ $+ ${EXT_LIBS} + +default all: compile + +compile: libglite_lbu_db.la + +check: + -echo No checks here yet. + +test_coverage: + -mkdir coverage + cd coverage && $(MAKE) -f ../Makefile top_srcdir=../../ COVERAGE_FLAGS="-fprofile-arcs -ftest-coverage" check + cd coverage && for i in `echo ${OBJS} | tr ' ' '\012' | sort -u`; do gcov $$i ; done + +examples: db_test db_expire + +doc: + doxygen C.dox + +stage: compile + $(MAKE) install PREFIX=${stagedir} DOSTAGE=yes + +dist: distsrc distbin + +distsrc: + mkdir -p ${top_srcdir}/${package}-${version} + cd ${top_srcdir} && GLOBIGNORE="${package}-${version}" && cp -Rf * ${package}-${version} + cd ${top_srcdir} && tar -czf ${distdir}/${package}-${version}_src.tar.gz --exclude-from=project/tar_exclude ${package}-${version} + rm -rf ${top_srcdir}/${package}-${version} + +distbin: + $(MAKE) install PREFIX=`pwd`/tmpbuilddir${stagedir} + save_dir=`pwd`; cd tmpbuilddir${stagedir} && tar -czf $$save_dir/${top_srcdir}/${distdir}/${package}-${version}_bin.tar.gz *; cd $$save_dir + rm -rf tmpbuilddir + +install: + -mkdir -p ${PREFIX}/lib + -mkdir -p ${PREFIX}/include/${globalprefix}/${lbutilsprefix} +# ${INSTALL} -m 644 ${top_srcdir}/LICENSE ${PREFIX}/share/doc/${package}-${version} + ${INSTALL} -m 755 "libglite_lbu_db.la" "${PREFIX}/lib/libglite_lbu_db.la"; \ + ${INSTALL} -m 644 ${top_srcdir}/interface/${HDRS} ${PREFIX}/include/${globalprefix}/${lbutilsprefix} + +clean: + +%.o %.lo: %.c + ${COMPILE} -c $< + +db.lo: db.c db.h +db_test.lo: libglite_lbu_dbtest.la db.h db_test.c + +.PHONY: default all compile check examples doc stage dist distsrc distbin install clean test_coverage diff --git a/org.glite.lbjp-utils.db/examples/db_expire.c b/org.glite.lbjp-utils.db/examples/db_expire.c new file mode 100644 index 0000000..012a91b --- /dev/null +++ b/org.glite.lbjp-utils.db/examples/db_expire.c @@ -0,0 +1,115 @@ +/* + * Example (and quick test) of prepared statements expirations. + * Use 'SET GLOBAL wait_timeout=...' for experimenting. + * + * Requires existing database with appropriate access and example table: + * + * mysqladmin -u root -p create test + * mysql -u root -p -e 'GRANT ALL on test.* to testuser@localhost' + * ./db_test + * + * Use CS environment variable when using different user/pwd@machine:dbname. + */ + +#include +#include +#include + +#include "db.h" + +#define CS "testuser/@localhost:test" +#define SELECT_CMD "SELECT id, user, info FROM data WHERE user = ?" + +#define dprintf(ARGS) { printf("%s: ", name); printf ARGS; } + + +static void print_blob(unsigned long len, char *blob) { + int i; + for (i = 0; i < len; i++) printf("%02X ", blob[i]); + printf("(='"); + for (i = 0; i < len; i++) printf("%c", blob[i]); + printf("')"); +} + + +static void print_free_result(const char *name, unsigned long *lens, char **res) { + dprintf((" id='%s'=%d\n", res[0], atoi(res[0]))); + + dprintf((" user='%s'\n", res[1])); + + dprintf((" blob=")); + if (res[2] && lens) print_blob(lens[2], res[2]); + else printf("null"); + printf("\n"); + + free(res[0]); + free(res[1]); + free(res[2]); +} + + +int main(int argn, char *argv[]) { + char *name, *user; + const char *cs; + glite_lbu_DBContext ctx; + glite_lbu_Statement stmt; + int caps, i, nr, c; + unsigned long lens[3]; + char *res[3]; + + if ((name = strrchr(argv[0], '/')) != NULL) name++; + else name = argv[0]; + if ((cs = getenv("CS")) == NULL) cs = CS; + + // init + dprintf(("connecting to %s...\n", cs)); + if (glite_lbu_InitDBContext(&ctx) != 0) goto fail; + if (glite_lbu_DBConnect(ctx, cs) != 0) goto failctx; + if ((caps = glite_lbu_DBQueryCaps(ctx)) == -1) goto failcon; + if ((caps & GLITE_LBU_DB_CAP_PREPARED) == 0) { + dprintf(("can't do prepared commands, exiting.")); + goto failcon; + } + // caps + glite_lbu_DBSetCaps(ctx, caps); + dprintf(("capabilities: %d\n", caps)); + + user = NULL; + dprintf(("preparing '%s'...\n", user)); + if ((glite_lbu_PrepareStmt(ctx, SELECT_CMD, &stmt)) != 0) goto failcon; + + do { + user = "cicomexocitl.civ"; + dprintf(("executing '%s'...\n", user)); + if (glite_lbu_ExecStmt(stmt, 1, GLITE_LBU_DB_TYPE_VARCHAR, user) == -1) goto failstmt; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) { + dprintf(("fetch '%s' failed\n", user)); + break; + } + dprintf(("\n")); + + c = fgetc(stdin); + } while (c != -1 && (c == '\r' || c == '\n')); + + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); + glite_lbu_FreeDBContext(ctx); + return 0; + +failstmt: + printf("closing stmt...\n"); + glite_lbu_FreeStmt(&stmt); +failcon: + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); +failctx: + glite_lbu_FreeDBContext(ctx); +fail: + dprintf(("failed\n")); + return 1; +} diff --git a/org.glite.lbjp-utils.db/examples/db_test.c b/org.glite.lbjp-utils.db/examples/db_test.c new file mode 100644 index 0000000..02be92a --- /dev/null +++ b/org.glite.lbjp-utils.db/examples/db_test.c @@ -0,0 +1,196 @@ +/* + * Example (and quick test) of this DB module. + * + * Requires existing database with appropriate access: + * + * mysqladmin -u root -p create test + * mysql -u root -p -e 'GRANT ALL on test.* to testuser@localhost' + * + * Use CS environment variable when using different user/pwd@machine:dbname. + */ + +#include +#include +#include + +#include "db.h" + +#define CS "testuser/@localhost:test" +#define CREATE_CMD "CREATE TABLE data (\n\ + id INT NOT NULL,\n\ + user VARCHAR(32) NOT NULL,\n\ + info BLOB,\n\ + PRIMARY KEY (id),\n\ + INDEX(user)\n\ +) engine=innodb" +#define DROP_CMD "DROP TABLE data" +#define INSERT_TRIO_CMD "INSERT INTO data (id, user, info) VALUES (%d, %s, %s)" +#define SELECT_TRIO_CMD "SELECT id, user, info FROM data WHERE user = '%s'" +#define INSERT_CMD "INSERT INTO data (id, user, info) VALUES (?, ?, ?)" +#define SELECT_CMD "SELECT id, user, info FROM data WHERE user = ?" + +#define dprintf(ARGS) { printf("%s: ", name); printf ARGS; } + + +static void print_blob(unsigned long len, char *blob) { + int i; + for (i = 0; i < len; i++) printf("%02X ", blob[i]); + printf("(='"); + for (i = 0; i < len; i++) printf("%c", blob[i]); + printf("')"); +} + + +static void print_free_result(const char *name, unsigned long *lens, char **res) { + dprintf((" id='%s'=%d\n", res[0], atoi(res[0]))); + + dprintf((" user='%s'\n", res[1])); + + dprintf((" blob=")); + if (res[2] && lens) print_blob(lens[2], res[2]); + else printf("null"); + printf("\n"); + + free(res[0]); + free(res[1]); + free(res[2]); +} + + +int main(int argn, char *argv[]) { + char *name, *cmd; + const char *cs; + glite_lbu_DBContext ctx; + glite_lbu_Statement stmt; + int caps; + + char blob1[] = "Guess: blob or \000string?"; + char blob2[] = {0, 1, 2, 3, 4, 5}; + + int nr; + char *res[3]; + unsigned long lens[3]; + + if ((name = strrchr(argv[0], '/')) != NULL) name++; + else name = argv[0]; + if ((cs = getenv("CS")) == NULL) cs = CS; + cmd = NULL; + + // init + dprintf(("connecting to %s...\n", cs)); + if (glite_lbu_InitDBContext(&ctx) != 0) goto fail; + if (glite_lbu_DBConnect(ctx, cs) != 0) goto failctx; + if ((caps = glite_lbu_DBQueryCaps(ctx)) == -1) goto failcon; + if ((caps & GLITE_LBU_DB_CAP_PREPARED) == 0) { + dprintf(("can't do prepared commands, exiting.")); + goto failcon; + } + // caps + glite_lbu_DBSetCaps(ctx, caps || GLITE_LBU_DB_CAP_ERRORS); + dprintf(("capabilities: %d\n", caps)); + // create all needed tables and data + dprintf(("creating tables...\n")); + glite_lbu_ExecSQL(ctx, DROP_CMD, NULL); + if (glite_lbu_ExecSQL(ctx, CREATE_CMD, NULL) == -1) goto failcon; + // trio-insert + dprintf(("trio-insert...\n")); + asprintf(&cmd, INSERT_TRIO_CMD, 1, "'hyperochus'", "NULL"); + if (glite_lbu_ExecSQL(ctx, cmd, NULL) != 1) goto failcon; + free(cmd); cmd = NULL; + // prepared-insert + dprintf(("prepare-insert...\n")); + if (glite_lbu_PrepareStmt(ctx, INSERT_CMD, &stmt) != 0) goto failcon; + dprintf(("execute 1. insert...\n")); + if (glite_lbu_ExecStmt(stmt, 3, + GLITE_LBU_DB_TYPE_INT, 2, + GLITE_LBU_DB_TYPE_VARCHAR, "cicomexocitl.civ", + GLITE_LBU_DB_TYPE_BLOB, blob1, sizeof(blob1) - 1) != 1) goto failstmt; + dprintf(("execute 2. insert...\n")); + if (glite_lbu_ExecStmt(stmt, 3, + GLITE_LBU_DB_TYPE_INT, 3, + GLITE_LBU_DB_TYPE_VARCHAR, "tartarus", + GLITE_LBU_DB_TYPE_NULL) != 1) goto failstmt; + dprintf(("execute 3. insert...\n")); + if (glite_lbu_ExecStmt(stmt, 3, + GLITE_LBU_DB_TYPE_INT, 4, + GLITE_LBU_DB_TYPE_VARCHAR, "harpia", + GLITE_LBU_DB_TYPE_BLOB, blob2, sizeof(blob2)) != 1) goto failstmt; + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); + + // trio-query +{ + const char *user; + + user = "harpia"; + dprintf(("selecting '%s'...\n", user)); + asprintf(&cmd, SELECT_TRIO_CMD, user); + if (glite_lbu_ExecSQL(ctx, cmd, &stmt) == -1) goto failcon; + free(cmd); cmd = NULL; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) dprintf(("fetch '%s' failed\n", user)); + dprintf(("closing stmt...\n")); + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); + + user = "nobody"; + dprintf(("selecting '%s'...\n", user)); + asprintf(&cmd, SELECT_TRIO_CMD, user); + if (glite_lbu_ExecSQL(ctx, cmd, &stmt) == -1) goto failcon; + free(cmd); cmd = NULL; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) dprintf(("fetch '%s' failed\n", user)); + dprintf(("closing stmt...\n")); + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); +} + + // "param" queries +{ + const char *user = NULL; + + dprintf(("preparing '%s'...\n", user)); + if ((glite_lbu_PrepareStmt(ctx, SELECT_CMD, &stmt)) != 0) goto failcon; + + user = "cicomexocitl.civ"; + dprintf(("executing '%s'...\n", user)); + if (glite_lbu_ExecStmt(stmt, 1, GLITE_LBU_DB_TYPE_VARCHAR, user) == -1) goto failstmt; + dprintf(("fetching '%s'...\n", user)); + while ((nr = glite_lbu_FetchRow(stmt, 3, lens, res)) > 0) { + dprintf(("Result: n=%d, res=%p\n", nr, res)); + print_free_result(name, lens, res); + } + if (nr < 0) dprintf(("fetch '%s' failed\n", user)); + dprintf(("\n")); + + dprintf(("closing stmt...\n")); + glite_lbu_FreeStmt(&stmt); + dprintf(("\n")); +} + + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); + glite_lbu_FreeDBContext(ctx); + return 0; + +failstmt: + printf("closing stmt...\n"); + glite_lbu_FreeStmt(&stmt); +failcon: + dprintf(("closing...\n")); + glite_lbu_DBClose(ctx); +failctx: + glite_lbu_FreeDBContext(ctx); +fail: + free(cmd); + dprintf(("failed\n")); + return 1; +} diff --git a/org.glite.lbjp-utils.db/interface/db.h b/org.glite.lbjp-utils.db/interface/db.h new file mode 100644 index 0000000..2818a4e --- /dev/null +++ b/org.glite.lbjp-utils.db/interface/db.h @@ -0,0 +1,344 @@ +#ifndef GLITE_LBU_DB_H +#define GLITE_LBU_DB_H + +#ident "$Header$" + + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * \file db.h + * \defgroup database Database module + * + * Database modul module API (LB & JP Utils). + * + * There are two ways to access DB here: + * - simple: + * + * SQL commands as single string. All values are incorporated in the SQL command strings. Proper escaping is required. + * - enhanced: + * + * Prepared SQL commands with separated parameters, functions PrepareStmt() and ExecStmt(). All values are delivered in separated buffers. Its faster for multiple using and more secure. + * @{ + */ + + +/** + * Enable transaction support if available. + * + * With disabled transaction can be used transaction functions, they are just ignored. + */ +#define GLITE_LBU_DB_CAP_TRANSACTIONS 1 + +/** + * Check prepared parameters support. + */ +#define GLITE_LBU_DB_CAP_PREPARED 2 + +/** + * Check for getting indexes support. + * + * Needed for QueryIndices call. + */ +#define GLITE_LBU_DB_CAP_INDEX 4 + + +/** + * Print all errors. + * + * Not returned from detection of capabilities. + */ +#define GLITE_LBU_DB_CAP_ERRORS 8 + + +/** + * Database connection context. + */ +typedef struct glite_lbu_DBContext_s *glite_lbu_DBContext; + + +/** + * Prepared statement, used for SQL statement with parameters. + */ +typedef struct glite_lbu_Statement_s *glite_lbu_Statement; + + +/** + * Structure holds date for multi-rows insert. + */ +typedef struct glite_lbu_bufInsert_s *glite_lbu_bufInsert; + + + +/** + * All types of parameteres, they match to the SQL types. + */ +typedef enum { + GLITE_LBU_DB_TYPE_NULL = 0, + GLITE_LBU_DB_TYPE_TINYINT = 1, + GLITE_LBU_DB_TYPE_INT = 2, + GLITE_LBU_DB_TYPE_TINYBLOB = 3, + GLITE_LBU_DB_TYPE_TINYTEXT = 4, + GLITE_LBU_DB_TYPE_BLOB = 5, + GLITE_LBU_DB_TYPE_TEXT = 6, + GLITE_LBU_DB_TYPE_MEDIUMBLOB = 7, + GLITE_LBU_DB_TYPE_MEDIUMTEXT = 8, + GLITE_LBU_DB_TYPE_LONGBLOB = 9, + GLITE_LBU_DB_TYPE_LONGTEXT = 10, + GLITE_LBU_DB_TYPE_VARCHAR = 11, + GLITE_LBU_DB_TYPE_CHAR = 12, + GLITE_LBU_DB_TYPE_DATE = 13, + GLITE_LBU_DB_TYPE_TIME = 14, + GLITE_LBU_DB_TYPE_DATETIME = 15, + GLITE_LBU_DB_TYPE_TIMESTAMP = 16, + GLITE_LBU_DB_TYPE_LAST = 17 +} glite_lbu_DBType; + + + +/** + * Get error state from DB context. + * + * \param[in] ctx context to work with + * \param[out] text error name + * \param[out] desc error description + */ +int glite_lbu_DBError(glite_lbu_DBContext ctx, char **text, char **desc); + + +/** + * Initialize the database context. + * + * \param[out] ctx result context + */ +int glite_lbu_InitDBContext(glite_lbu_DBContext *ctx); + + +/** + * Free database context. + */ +void glite_lbu_FreeDBContext(glite_lbu_DBContext ctx); + + +/** + * Connect to the given database. + * + * \param[out] ctx context to work with + * \param[in] cs connect string user/password\@host:database + * + * \return error code, 0 = OK + */ +int glite_lbu_DBConnect(glite_lbu_DBContext ctx, const char *cs); + + +/** + * Close the connection. + * + * \param[in,out] ctx context to work with + */ +void glite_lbu_DBClose(glite_lbu_DBContext ctx); + + +/** + * Check database version and capabilities. + * + * \param[in,out] ctx context to work with + * + * \return capabilities + * \retval -1 error occured + */ +int glite_lbu_DBQueryCaps(glite_lbu_DBContext ctx); + + +/** + * Set the database capabilities on already initialized context. + * + * It should be find out by DBQueryCaps() first. + * + * \param[in,out] ctx context to work with + * \param[in] caps capabilities to use, should be found out by QueryCaps() + */ +void glite_lbu_DBSetCaps(glite_lbu_DBContext ctx, int caps); + + +/** + * Start transaction. + */ +int glite_lbu_Transaction(glite_lbu_DBContext ctx); + + +/** + * Commit (end) transaction. + */ +int glite_lbu_Commit(glite_lbu_DBContext ctx); + + +/** + * Cancel transaction. + */ +int glite_lbu_Rollback(glite_lbu_DBContext ctx); + + +/** + * \param[in,out] stmt executed SQL statement + * \param[in] n number of items for sure there is enough space in lengths and results + * \param[out] lengths array with lengths (good for data blobs), may be NULL + * \param[out] results array with results, all items are allocated + * + * \retval >0 number of fields of the retrieved row + * \retval 0 no more rows + * \retval -1 error + */ +int glite_lbu_FetchRow(glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results); + + +/** + * Free the statement structure and destroy its parameters. + * + * Statement will be set to NULL and multiple calls are allowed. + * + * \param[in,out] stmt statement + */ +void glite_lbu_FreeStmt(glite_lbu_Statement *stmt); + + +/** + * Parse and execute one simple SQL statement. + * All values are incorporated int the SQL command string. + * + * \param[in,out] ctx context to work with + * \param[in] cmd SQL command + * \param[out] stmt statement handle with results (makes sense for selects only) + * + * \return number of rows selected, created or affected by update, -1 on error + */ +int glite_lbu_ExecSQL(glite_lbu_DBContext ctx, const char *cmd, glite_lbu_Statement *stmt); + + +/** + * Query for column names of the statement. + * + * It work only for simple API, so only after ExecSQL(). + * + * \param[in,out] stmt the statement handle + * \param[out] cols result array of names + * + * \return error code + */ +int glite_lbu_QueryColumns(glite_lbu_Statement stmt, char **cols); + + +/** + * Retrieve column names of a query simple SQL statement. + * + * \param[in,out] ctx context to work with + * \param[in] table table name + * \param[out] key_names one-dimensional index names array + * \param[out] column_names two-dimensional column names array + * + * \return 0 if OK, nonzero on error + */ +int glite_lbu_QueryIndices(glite_lbu_DBContext ctx, const char *table, char ***key_names, char ****column_names); + + +/** + * Convert time_t into database-specific time string. + * + * The result string can be used directly in SQL commands. + * + * \param[in] t the converted time + * \param[out] str result allocated string + */ +void glite_lbu_TimeToDB(time_t t, char **str); + + +/** + * Convert database-specific time string to time_t. + * + * String is expected in database for (ISO format). + * + * \param[in] str the converted string + * \return result time + */ +time_t glite_lbu_DBToTime(const char *str); + + +/** + * Init data structure for buffered insert + * + * takes table_name and columns string for future multirow insert + * when insert string oversize size_limit or number of rows to be inserted + * overcome record_limit, the real insert is triggered + */ +int glite_lbu_bufferedInsertInit(glite_lbu_DBContext ctx, glite_lbu_bufInsert *bi, void *mysql, const char *table_name, long size_limit, long record_limit, const char * columns); + + +/** + * adds row of n values into n columns into an insert buffer + * if num. of rows or size of data oversteps the limits, real + * multi-row insert is done + */ +int glite_lbu_bufferedInsert(glite_lbu_bufInsert bi, const char *row); + + +/** + * Flush buffered data and free bi structure. + */ +int glite_lbu_bufferedInsertClose(glite_lbu_bufInsert bi); + + +/** + * Prepare the SQL statement. Use glite_lbu_FreeStmt() to free it. + * + * \param[in,out] ctx context to work with + * \param[in] sql SQL command + * \param[out] stmt returned SQL statement + * + * \return error code + */ +int glite_lbu_PrepareStmt(glite_lbu_DBContext ctx, const char *sql, glite_lbu_Statement *stmt); + + +/** + * Execute prepared SQL statement. + * + * \param[in,out] stmt SQL statement + * \param[in] n number of items + * + * Variable parameters (n-times): + * + * always: + * + * \param type DB item type + * + * then one of them: + * + * \param GLITE_LBU_DB_TYPE_TINYINT int c + * \param GLITE_LBU_DB_TYPE_INT long int i + * \param GLITE_LBU_DB_TYPE_...BLOB/TEXT void *b, unsigned long len + * \param GLITE_LBU_DB_TYPE_[VAR]CHAR char *str + * \param GLITE_LBU_DB_TYPE_DATE/TIME/DATETIME time_t t + * \param GLITE_LBU_DB_TYPE_TIMESTAMP time_t t + * \param GLITE_LBU_DB_TYPE_NULL - + * + * \return number of affected rows, -1 on error + */ +int glite_lbu_ExecStmt(glite_lbu_Statement stmt, int n, ...); + + +/** + * @} database group + */ + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/org.glite.lbjp-utils.db/src/db.c b/org.glite.lbjp-utils.db/src/db.c new file mode 100644 index 0000000..bce48b8 --- /dev/null +++ b/org.glite.lbjp-utils.db/src/db.c @@ -0,0 +1,1039 @@ +#ident "$Header$" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "glite/lbu/trio.h" +#include "db.h" + + +#define GLITE_LBU_MYSQL_INDEX_VERSION 40001 +#define GLITE_LBU_MYSQL_PREPARED_VERSION 40102 +#define BUF_INSERT_ROW_ALLOC_BLOCK 1000 +#ifndef GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH +#define GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH 256 +#endif + + +#define CLR_ERR(CTX) lbu_clrerr((CTX)) +#define ERR(CTX, CODE, DESC) lbu_err((CTX), (CODE), (DESC), __FUNCTION__, __LINE__) +#define STATUS(CTX) ((CTX)->err.code) +#define MY_ERR(CTX) myerr((CTX), __FUNCTION__, __LINE__) +#define MY_ERRSTMT(STMT) myerrstmt((STMT), __FUNCTION__, __LINE__) +#define MY_ISOKSTMT(STMT, RETRY) myisokstmt((STMT), __FUNCTION__, __LINE__, (RETRY)) + +#define USE_TRANS(CTX) ((CTX->caps & GLITE_LBU_DB_CAP_TRANSACTIONS) != 0) + +#define dprintf(CTX, FMT...) if (CTX->caps & GLITE_LBU_DB_CAP_ERRORS) fprintf(stderr, ##FMT) + + +struct glite_lbu_DBContext_s { + MYSQL *mysql; + const char *cs; + int have_caps; + int caps; + struct { + int code; + char *desc; + } err; +}; + + +struct glite_lbu_Statement_s { + glite_lbu_DBContext ctx; + + /* for simple commands */ + MYSQL_RES *result; + + /* for prepared commands */ + MYSQL_STMT *stmt; + unsigned long nrfields; +}; + + +struct glite_lbu_bufInsert_s { + glite_lbu_DBContext ctx; + char *table_name; + char *columns; /* names of columns to be inserted into + * (values separated with commas) */ + char **rows; /* each row hold string of one row to be inserted + * (values separated with commas) */ + long rec_num, /* actual number of rows in structure */ + rec_size; /* approx. size of a real insert string */ + long size_limit, /* size and # of records limit which trigger */ + record_limit; /* real insert; zero means unlimitted */ +}; + + +/* + * mapping glite DB types to mysql types + */ +int glite_type_to_mysql[] = { + MYSQL_TYPE_NULL, + MYSQL_TYPE_TINY, + MYSQL_TYPE_LONG, + MYSQL_TYPE_TINY_BLOB, + MYSQL_TYPE_TINY_BLOB, + MYSQL_TYPE_BLOB, + MYSQL_TYPE_BLOB, + MYSQL_TYPE_MEDIUM_BLOB, + MYSQL_TYPE_MEDIUM_BLOB, + MYSQL_TYPE_LONG_BLOB, + MYSQL_TYPE_LONG_BLOB, + MYSQL_TYPE_VAR_STRING, + MYSQL_TYPE_STRING, + MYSQL_TYPE_DATE, + MYSQL_TYPE_TIME, + MYSQL_TYPE_DATETIME, + MYSQL_TYPE_TIMESTAMP, +}; + + + +static int lbu_clrerr(glite_lbu_DBContext ctx); +static int lbu_err(glite_lbu_DBContext ctx, int code, const char *desc, const char *func, int line); +static int myerr(glite_lbu_DBContext ctx, const char *source, int line); +static int myerrstmt(glite_lbu_Statement stmt, const char *source, int line); +static int myisokstmt(glite_lbu_Statement stmt, const char *source, int line, int *retry); +static int db_connect(glite_lbu_DBContext ctx, const char *cs, MYSQL **mysql); +static void db_close(MYSQL *mysql); +static int transaction_test(glite_lbu_DBContext ctx, MYSQL *m2, int *have_transactions); +static int FetchRowSimple(glite_lbu_DBContext ctx, MYSQL_RES *result, unsigned long *lengths, char **results); +static int FetchRowPrepared(glite_lbu_DBContext ctx, glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results); +void set_time(MYSQL_TIME *mtime, const time_t time); +time_t get_time(const MYSQL_TIME *mtime); + + +/* ---- common ---- */ + + +int glite_lbu_DBError(glite_lbu_DBContext ctx, char **text, char **desc) { + if (text) *text = strdup(strerror(ctx->err.code)); + if (desc) { + if (ctx->err.desc) *desc = strdup(ctx->err.desc); + else *desc = NULL; + } + + return ctx->err.code; +} + + +int glite_lbu_InitDBContext(glite_lbu_DBContext *ctx) { + *ctx = calloc(1, sizeof **ctx); + return *ctx == NULL ? ENOMEM : 0; +} + + +void glite_lbu_FreeDBContext(glite_lbu_DBContext ctx) { + if (ctx) { + assert(ctx->mysql == NULL); + free(ctx->err.desc); + free(ctx); + } +} + + +int glite_lbu_DBConnect(glite_lbu_DBContext ctx, const char *cs) { + if (db_connect(ctx, cs, &ctx->mysql) != 0) return STATUS(ctx); + return 0; +} + + +void glite_lbu_DBClose(glite_lbu_DBContext ctx) { + db_close(ctx->mysql); + ctx->mysql = NULL; +} + + +int glite_lbu_DBQueryCaps(glite_lbu_DBContext ctx) { + MYSQL *m = ctx->mysql; + MYSQL *m2; + int major,minor,sub,version,caps,have_transactions=0; + const char *ver_s; + + if (ctx->have_caps) return ctx->caps; + + caps = 0; + + ver_s = mysql_get_server_info(m); + if (!ver_s || 3 != sscanf(ver_s,"%d.%d.%d",&major,&minor,&sub)) + return ERR(ctx, EINVAL, "problem retreiving MySQL version"); + version = 10000*major + 100*minor + sub; + + if (version >= GLITE_LBU_MYSQL_INDEX_VERSION) caps |= GLITE_LBU_DB_CAP_INDEX; + if (version >= GLITE_LBU_MYSQL_PREPARED_VERSION) caps |= GLITE_LBU_DB_CAP_PREPARED; + + CLR_ERR(ctx); + + if (db_connect(ctx, ctx->cs, &m2) == 0) { + transaction_test(ctx, m2, &have_transactions); + db_close(m2); + } + if (have_transactions) caps |= GLITE_LBU_DB_CAP_TRANSACTIONS; + + if (STATUS(ctx) == 0) { + ctx->have_caps = 1; + return caps; + } else return -1; +} + + +void glite_lbu_DBSetCaps(glite_lbu_DBContext ctx, int caps) { + ctx->caps = caps; +} + + +int glite_lbu_Transaction(glite_lbu_DBContext ctx) { + if (USE_TRANS(ctx)) { + if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=0", NULL) < 0) goto err; + if (glite_lbu_ExecSQL(ctx, "BEGIN", NULL) < 0) goto err; + } +err: + return STATUS(ctx); +} + + +int glite_lbu_Commit(glite_lbu_DBContext ctx) { + if (USE_TRANS(ctx)) { + if (glite_lbu_ExecSQL(ctx, "COMMIT", NULL) < 0) goto err; + if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=1", NULL) < 0) goto err; + } +err: + return STATUS(ctx); +} + + +int glite_lbu_Rollback(glite_lbu_DBContext ctx) { + if (USE_TRANS(ctx)) { + if (glite_lbu_ExecSQL(ctx, "ROLLBACK", NULL) < 0) goto err; + if (glite_lbu_ExecSQL(ctx, "SET AUTOCOMMIT=1", NULL) < 0) goto err; + } +err: + return STATUS(ctx); +} + + +int glite_lbu_FetchRow(glite_lbu_Statement stmt, unsigned int n, unsigned long *lengths, char **results) { + memset(results, 0, n * sizeof(*results)); + if (stmt->result) return FetchRowSimple(stmt->ctx, stmt->result, lengths, results); + else return FetchRowPrepared(stmt->ctx, stmt, n, lengths, results); +} + + +void glite_lbu_FreeStmt(glite_lbu_Statement *stmt) { + if (*stmt) { + if ((*stmt)->result) mysql_free_result((*stmt)->result); + if ((*stmt)->stmt) mysql_stmt_close((*stmt)->stmt); + free(*stmt); + *stmt = NULL; + } +} + + +int glite_lbu_QueryIndices(glite_lbu_DBContext ctx, const char *table, char ***key_names, char ****column_names) { + glite_lbu_Statement stmt = NULL; + + int i,j,ret; + +/* XXX: "show index from" columns. Matches at least MySQL 4.0.11 */ + char *showcol[12]; + int Key_name,Seq_in_index,Column_name,Sub_part; + + char **keys = NULL; + int *cols = NULL; + char **col_names = NULL; + + int nkeys = 0; + + char ***idx = NULL; + + Key_name = Seq_in_index = Column_name = Sub_part = -1; + + if (glite_lbu_ExecSQL(ctx,"show index from states",&stmt)<0) + return STATUS(ctx); + + while ((ret = glite_lbu_FetchRow(stmt,sizeof(showcol)/sizeof(showcol[0]),NULL,showcol)) > 0) { + assert(ret <= sizeof showcol/sizeof showcol[0]); + + if (!col_names) { + col_names = malloc(ret * sizeof col_names[0]); + glite_lbu_QueryColumns(stmt,col_names); + for (i=0; i= 0 && Seq_in_index >= 0 && + Column_name >= 0 && Sub_part >= 0); + + } + + for (i=0; imysql, cmd)) { + /* error occured */ + switch (merr = mysql_errno(ctx->mysql)) { + case 0: + break; + case ER_DUP_ENTRY: + ERR(ctx, EEXIST, mysql_error(ctx->mysql)); + return -1; + break; + case CR_SERVER_LOST: + if (retry_nr <= 0) + do_reconnect = 1; + break; + default: + MY_ERR(ctx); + return -1; + break; + } + } + retry_nr++; + } + + if (stmt) { + *stmt = calloc(1, sizeof(**stmt)); + if (!*stmt) { + ERR(ctx, ENOMEM, NULL); + return -1; + } + (**stmt).ctx = ctx; + (**stmt).result = mysql_store_result(ctx->mysql); + if (!(**stmt).result) { + if (mysql_errno(ctx->mysql)) { + MY_ERR(ctx); + *stmt = NULL; + return -1; + } + } + } else { + MYSQL_RES *r = mysql_store_result(ctx->mysql); + mysql_free_result(r); + } +#ifdef LBS_DB_PROFILE + pid = getpid(); + gettimeofday(&end,NULL); + end.tv_usec -= start.tv_usec; + end.tv_sec -= start.tv_sec; + if (end.tv_usec < 0) { end.tv_sec--; end.tv_usec += 1000000; } + + sum.tv_usec += end.tv_usec; + sum.tv_sec += end.tv_sec + sum.tv_usec / 1000000; + sum.tv_usec -= 1000000 * (sum.tv_usec / 1000000); + fprintf(stderr,"[%d] %s\n[%d] %3ld.%06ld (sum: %3ld.%06ld)\n",pid,txt,pid,end.tv_sec,end.tv_usec,sum.tv_sec,sum.tv_usec); +#endif + + return mysql_affected_rows(ctx->mysql); +} + + +int glite_lbu_QueryColumns(glite_lbu_Statement stmt, char **cols) +{ + int i = 0; + MYSQL_FIELD *f; + + if (!stmt->result) return ERR(stmt->ctx, EINVAL, "QueryColumns implemented only for simple API"); + while ((f = mysql_fetch_field(stmt->result))) cols[i++] = f->name; + return i == 0; +} + + +void glite_lbu_TimeToDB(time_t t, char **str) { + struct tm *tm = gmtime(&t); + + asprintf(str,"'%4d-%02d-%02d %02d:%02d:%02d'",tm->tm_year+1900,tm->tm_mon+1, + tm->tm_mday,tm->tm_hour,tm->tm_min,tm->tm_sec); +} + + +time_t glite_lbu_DBToTime(const char *str) { + struct tm tm; + + memset(&tm,0,sizeof(tm)); + setenv("TZ","UTC",1); tzset(); + sscanf(str,"%4d-%02d-%02d %02d:%02d:%02d", + &tm.tm_year,&tm.tm_mon,&tm.tm_mday, + &tm.tm_hour,&tm.tm_min,&tm.tm_sec); + tm.tm_year -= 1900; + tm.tm_mon--; + + return mktime(&tm); +} + +/* ---- prepared --- */ + +int glite_lbu_PrepareStmt(glite_lbu_DBContext ctx, const char *sql, glite_lbu_Statement *stmt) { + int ret, retry; + MYSQL_RES *meta; + + // init + *stmt = calloc(1, sizeof(**stmt)); + (*stmt)->ctx = ctx; + + // create the SQL command + if (((*stmt)->stmt = mysql_stmt_init(ctx->mysql)) == NULL) + return MY_ERRSTMT(*stmt); + + // prepare the SQL command + retry = 1; + do { + mysql_stmt_prepare((*stmt)->stmt, sql, strlen(sql)); + ret = MY_ISOKSTMT(*stmt, &retry); + } while (ret == 0); + if (ret == -1) goto failed; + + // number of fields (0 for no results) + if ((meta = mysql_stmt_result_metadata((*stmt)->stmt)) != NULL) { + (*stmt)->nrfields = mysql_num_fields(meta); + mysql_free_result(meta); + } else + (*stmt)->nrfields = 0; + + return CLR_ERR(ctx); + +failed: + glite_lbu_FreeStmt(stmt); + return STATUS(ctx); +} + + +int glite_lbu_ExecStmt(glite_lbu_Statement stmt, int n, ...) { + int i; + va_list ap; + glite_lbu_DBType type; + char *pchar; + long int *plint; + MYSQL_TIME *ptime; + glite_lbu_DBContext ctx; + int ret, retry; + MYSQL_BIND *binds = NULL; + void **data = NULL; + unsigned long *lens; + + // gather parameters + if (n) { + binds = calloc(n, sizeof(MYSQL_BIND)); + data = calloc(n, sizeof(void *)); + lens = calloc(n, sizeof(unsigned long *)); + } + va_start(ap, n); + for (i = 0; i < n; i++) { + type = va_arg(ap, glite_lbu_DBType); + switch (type) { + case GLITE_LBU_DB_TYPE_TINYINT: + pchar = binds[i].buffer = data[i] = malloc(sizeof(char)); + *pchar = va_arg(ap, int); + break; + + case GLITE_LBU_DB_TYPE_INT: + plint = binds[i].buffer = data[i] = malloc(sizeof(long int)); + *plint = va_arg(ap, long int); + break; + + case GLITE_LBU_DB_TYPE_TINYBLOB: + case GLITE_LBU_DB_TYPE_TINYTEXT: + case GLITE_LBU_DB_TYPE_BLOB: + case GLITE_LBU_DB_TYPE_TEXT: + case GLITE_LBU_DB_TYPE_MEDIUMBLOB: + case GLITE_LBU_DB_TYPE_MEDIUMTEXT: + case GLITE_LBU_DB_TYPE_LONGBLOB: + case GLITE_LBU_DB_TYPE_LONGTEXT: + binds[i].buffer = va_arg(ap, void *); + binds[i].length = &lens[i]; + lens[i] = va_arg(ap, unsigned long); + break; + + case GLITE_LBU_DB_TYPE_VARCHAR: + case GLITE_LBU_DB_TYPE_CHAR: + binds[i].buffer = va_arg(ap, char *); + binds[i].length = &lens[i]; + lens[i] = binds[i].buffer ? strlen((char *)binds[i].buffer) : 0; + break; + + case GLITE_LBU_DB_TYPE_DATE: + case GLITE_LBU_DB_TYPE_TIME: + case GLITE_LBU_DB_TYPE_DATETIME: + case GLITE_LBU_DB_TYPE_TIMESTAMP: + ptime = binds[i].buffer = data[i] = malloc(sizeof(MYSQL_TIME)); + set_time(ptime, va_arg(ap, time_t)); + break; + + case GLITE_LBU_DB_TYPE_NULL: + break; + + default: + assert("unimplemented parameter assign" == NULL); + break; + } + binds[i].buffer_type = glite_type_to_mysql[type]; + } + va_end(ap); + + // bind parameters + if (mysql_stmt_bind_param(stmt->stmt, binds) != 0) { + MY_ERRSTMT(stmt); + goto failed; + } + + // run + ctx = stmt->ctx; + retry = 1; + do { + mysql_stmt_execute(stmt->stmt); + ret = MY_ISOKSTMT(stmt, &retry); + } while (ret == 0); + if (ret == -1) goto failed; + + // result + retry = 1; + do { + mysql_stmt_store_result(stmt->stmt); + ret = MY_ISOKSTMT(stmt, &retry); + } while (ret == 0); + if (ret == -1) goto failed; + + // free params + for (i = 0; i < n; i++) free(data[i]); + free(data); + free(binds); + free(lens); + CLR_ERR(ctx); + return mysql_stmt_affected_rows(stmt->stmt); + +failed: + for (i = 0; i < n; i++) free(data[i]); + free(data); + free(binds); + free(lens); + return -1; +} + + +int glite_lbu_bufferedInsertInit(glite_lbu_DBContext ctx, glite_lbu_bufInsert *bi, void *mysql, const char *table_name, long size_limit, long record_limit, const char *columns) +{ + *bi = calloc(1, sizeof(*bi)); + (*bi)->ctx = ctx; + (*bi)->table_name = strdup(table_name); + (*bi)->columns = strdup(columns); + (*bi)->rec_num = 0; + (*bi)->rec_size = 0; + (*bi)->rows = calloc(record_limit, sizeof(*((*bi)->rows)) ); + (*bi)->size_limit = size_limit; + (*bi)->record_limit = record_limit; + + return CLR_ERR(ctx); +} + + +static int flush_bufferd_insert(glite_lbu_bufInsert bi) +{ + char *stmt, *vals, *temp; + long i; + + + if (!bi->rec_num) + return STATUS(bi->ctx); + + asprintf(&vals,"(%s)", bi->rows[0]); + for (i=1; i < bi->rec_num; i++) { + // XXX: use string add (preallocated memory) + asprintf(&temp,"%s,(%s)", vals, bi->rows[i]); + free(vals); vals = temp; temp = NULL; + free(bi->rows[i]); + bi->rows[i] = NULL; + } + + trio_asprintf(&stmt, "insert into %|Ss(%|Ss) values %s;", + bi->table_name, bi->columns, vals); + + if (glite_lbu_ExecSQL(bi->ctx,stmt,NULL) < 0) { + if (STATUS(bi->ctx) == EEXIST) + CLR_ERR(bi->ctx); + } + + /* reset bi counters */ + bi->rec_size = 0; + bi->rec_num = 0; + + free(vals); + free(stmt); + + return STATUS(bi->ctx); +} + + +int glite_lbu_bufferedInsert(glite_lbu_bufInsert bi, const char *row) +{ + bi->rows[bi->rec_num++] = strdup(row); + bi->rec_size += strlen(row); + + if ((bi->size_limit && bi->rec_size >= bi->size_limit) || + (bi->record_limit && bi->rec_num >= bi->record_limit)) + { + if (flush_bufferd_insert(bi)) + return STATUS(bi->ctx); + } + + return CLR_ERR(bi->ctx); +} + + +static void free_buffered_insert(glite_lbu_bufInsert bi) { + long i; + + free(bi->table_name); + free(bi->columns); + for (i=0; i < bi->rec_num; i++) { + free(bi->rows[i]); + } + free(bi->rows); +} + + +int glite_lbu_bufferedInsertClose(glite_lbu_bufInsert bi) +{ + if (flush_bufferd_insert(bi)) + return STATUS(bi->ctx); + free_buffered_insert(bi); + + return CLR_ERR(bi->ctx); +} + + +/* + * helping compatibility function: clear error from the context + */ +static int lbu_clrerr(glite_lbu_DBContext ctx) { + ctx->err.code = 0; + if (ctx->err.desc) { + free(ctx->err.desc); + ctx->err.desc = NULL; + } + return 0; +} + + +/* + * helping compatibility function: sets error on the context + */ +static int lbu_err(glite_lbu_DBContext ctx, int code, const char *desc, const char *func, int line) { + if (code) { + ctx->err.code = code; + free(ctx->err.desc); + ctx->err.desc = desc ? strdup(desc) : NULL; + dprintf(ctx, "[db %d] %s:%d %s\n", getpid(), func, line, desc); + return code; + } else + return ctx->err.code; +} + + +/* + * helping function: find oud mysql error and sets on the context + */ +static int myerr(glite_lbu_DBContext ctx, const char *source, int line) { + return lbu_err(ctx, EIO, mysql_error(ctx->mysql), source, line); +} + + +/* + * helping function: find oud mysql stmt error and sets on the context + */ +static int myerrstmt(glite_lbu_Statement stmt, const char *source, int line) { + return lbu_err(stmt->ctx, EIO, mysql_stmt_error(stmt->stmt), source, line); +} + + +/* + * Ehelping function: error handle + * + * \return -1 failed + * \return 0 retry + * \return 1 OK + */ +static int myisokstmt(glite_lbu_Statement stmt, const char *source, int line, int *retry) { + switch (mysql_stmt_errno(stmt->stmt)) { + case 0: + return 1; + break; + case ER_DUP_ENTRY: + lbu_err(stmt->ctx, EEXIST, mysql_stmt_error(stmt->stmt), source, line); + return -1; + break; + case CR_SERVER_LOST: + if (*retry > 0) { + (*retry)--; + return 0; + } else + return -1; + break; + default: + myerrstmt(stmt, source, line); + return -1; + break; + } +} + + +/* + * mysql connect + */ +static int db_connect(glite_lbu_DBContext ctx, const char *cs, MYSQL **mysql) { + char *buf = NULL; + char *host,*user,*pw,*db; + char *slash,*at,*colon; + int ret; + + // needed for SQL result parameters + assert(sizeof(int) >= sizeof(my_bool)); + + if (!cs) return ERR(ctx, EINVAL, "connect string not specified"); + + if (!(*mysql = mysql_init(NULL))) return ERR(ctx, ENOMEM, NULL); + + mysql_options(*mysql, MYSQL_READ_DEFAULT_FILE, "my"); + + host = user = pw = db = NULL; + + buf = strdup(cs); + slash = strchr(buf,'/'); + at = strrchr(buf,'@'); + colon = strrchr(buf,':'); + + if (!slash || !at || !colon) { + free(buf); + db_close(*mysql); + *mysql = NULL; + return ERR(ctx, EINVAL, "Invalid DB connect string"); + } + + *slash = *at = *colon = 0; + host = at+1; + user = buf; + pw = slash+1; + db = colon+1; + + /* ljocha: CLIENT_FOUND_ROWS added to make authorization check + * working in update_notif(). + * Hope it does not break anything else */ + if (!mysql_real_connect(*mysql,host,user,pw,db,0,NULL,CLIENT_FOUND_ROWS)) { + free(buf); + ret = MY_ERR(ctx); + glite_lbu_DBClose(ctx); + return ret; + } + free(buf); + + ctx->cs = cs; + return 0; +} + + +/* + * mysql close + */ +static void db_close(MYSQL *mysql) { + if (mysql) mysql_close(mysql); +} + + +/* + * test transactions capability: + * + * 1) with connection 1 create testing table test + * 2) with connection 1 insert a value + * 3) with connection 2 look for a value, transactions are for no error and + * no items found + * 4) with connection 1 commit and drop the table + */ +static int transaction_test(glite_lbu_DBContext ctx, MYSQL *m2, int *have_transactions) { + MYSQL *m1; + char *desc, *cmd_create, *cmd_insert, *cmd_select, *cmd_drop; + int retval; + int err; + pid_t pid; + + ctx->caps |= GLITE_LBU_DB_CAP_TRANSACTIONS; + pid = getpid(); + *have_transactions = 0; + + asprintf(&cmd_create, "CREATE TABLE test%d (item INT) ENGINE='innodb'", pid); + asprintf(&cmd_insert, "INSERT INTO test%d (item) VALUES (1)", pid); + asprintf(&cmd_select, "SELECT item FROM test%d", pid); + asprintf(&cmd_drop, "DROP TABLE test%d", pid); + + m1 = ctx->mysql; + //glite_lbu_ExecSQL(ctx, cmd_drop, NULL); + if (glite_lbu_ExecSQL(ctx, cmd_create, NULL) != 0) goto err1; + if (glite_lbu_Transaction(ctx) != 0) goto err2; + if (glite_lbu_ExecSQL(ctx, cmd_insert, NULL) != 1) goto err2; + + ctx->mysql = m2; + if ((retval = glite_lbu_ExecSQL(ctx, cmd_select, NULL)) == -1) goto err2; + + ctx->mysql = m1; + if (glite_lbu_Commit(ctx) != 0) goto err2; + if (glite_lbu_ExecSQL(ctx, cmd_drop, NULL) != 0) goto err1; + +#ifdef LBS_DB_PROFILE + fprintf(stderr, "[%d] use_transactions = %d\n", getpid(), USE_TRANS(ctx)); +#endif + + *have_transactions = retval == 0; + goto ok; +err2: + err = ctx->err.code; + desc = ctx->err.desc; + glite_lbu_ExecSQL(ctx, cmd_drop, NULL); + ctx->err.code = err; + ctx->err.desc = desc; +err1: +ok: + free(cmd_create); + free(cmd_insert); + free(cmd_select); + free(cmd_drop); + return STATUS(ctx); +} + + +/* + * simple version of the fetch + */ +static int FetchRowSimple(glite_lbu_DBContext ctx, MYSQL_RES *result, unsigned long *lengths, char **results) { + MYSQL_ROW row; + int nr, i; + unsigned long *len; + + CLR_ERR(ctx); + + if (!(row = mysql_fetch_row(result))) { + if (mysql_errno((MYSQL *) ctx->mysql)) { + MY_ERR(ctx); + return -1; + } else return 0; + } + + nr = mysql_num_fields(result); + len = mysql_fetch_lengths(result); + for (i=0; inrfields) { + ERR(ctx, EINVAL, "bad number of result fields"); + return -1; + } + + // space for results + if (n) binds = calloc(n, sizeof(MYSQL_BIND)); + if (!lengths) { + lens = calloc(n, sizeof(unsigned long)); + lengths = lens; + } + for (i = 0; i < n; i++) { + binds[i].buffer_type = MYSQL_TYPE_VAR_STRING; + binds[i].buffer_length = GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH - 1; + binds[i].length = &lengths[i]; + binds[i].buffer = results[i] = calloc(1, GLITE_LBU_DEFAULT_RESULT_BUFFER_LENGTH); + } + if (mysql_stmt_bind_result(stmt->stmt, binds) != 0) goto failedstmt; + + // fetch data, all can be truncated + retry = 1; + do { + switch(mysql_stmt_fetch(stmt->stmt)) { +#ifdef MYSQL_DATA_TRUNCATED + case MYSQL_DATA_TRUNCATED: +#endif + case 0: + ret = 1; break; + case 1: ret = MY_ISOKSTMT(stmt, &retry); break; + case MYSQL_NO_DATA: ret = 0; goto quit; /* it's OK */ + default: ERR(ctx, EIO, "other fetch error"); goto failed; + } + } while (ret == 0); + if (ret == -1) goto failed; + + // check if all fileds had enough buffer space + for (i = 0; i < n; i++) { + // fetch the rest if needed + if (lengths[i] > binds[i].buffer_length) { + unsigned int fetched; + + fetched = binds[i].buffer_length; + if ((results[i] = realloc(results[i], lengths[i] + 1)) == NULL) { + ERR(ctx, ENOMEM, "insufficient memory for field data"); + goto failed; + } + results[i][lengths[i]] = '\000'; + binds[i].buffer = results[i] + fetched; + binds[i].buffer_length = lengths[i] - fetched; + + retry = 1; + do { + switch (mysql_stmt_fetch_column(stmt->stmt, binds + i, i, fetched)) { + case 0: ret = 1; break; + case 1: ret = MY_ISOKSTMT(stmt, &retry); break; + case MYSQL_NO_DATA: ret = 0; goto quit; /* it's OK */ + default: ERR(ctx, EIO, "other fetch error"); goto failed; + } + } while (ret == 0); + if (ret == -1) goto failed; + } + } + + CLR_ERR(ctx); + free(binds); + free(lens); + return n; + +failedstmt: + MY_ERRSTMT(stmt); +failed: + ret = -1; +quit: + free(binds); + free(lens); + for (i = 0; i < n; i++) { + free(results[i]); + results[i] = NULL; + } + return ret; +} + + +void set_time(MYSQL_TIME *mtime, const time_t time) { + struct tm tm; + + gmtime_r(&time, &tm); + memset(mtime, 0, sizeof *mtime); + mtime->year = tm.tm_year + 1900; + mtime->month = tm.tm_mon + 1; + mtime->day = tm.tm_mday; + mtime->hour = tm.tm_hour; + mtime->minute = tm.tm_min; + mtime->second = tm.tm_sec; +} + + +time_t get_time(const MYSQL_TIME *mtime) { + struct tm tm; + + memset(&tm, 0, sizeof(tm)); + setenv("TZ","UTC",1); tzset(); + tm.tm_year = mtime->year - 1900; + tm.tm_mon = mtime->month - 1; + tm.tm_mday = mtime->day; + tm.tm_hour = mtime->hour; + tm.tm_min = mtime->minute; + tm.tm_sec = mtime->second; + + return mktime(&tm); +} -- 1.8.2.3