// Project: Scheme-PG // Author: David J. Neu, djneu@acm.org // Maintainer: David J. Neu, djneu@acm.org // Project Home Page: http://scheme-pg.sourceforge.net // Copyright: Copyright (c) 2004 Universal Technical Resource Services, Inc. // License: MIT License, see license.txt // CVS Id: $Id: s-pg.c,v 1.10 2005/03/21 22:58:18 djneu Exp $ /* 208 to 299 migration notes: SCHEME_STRINGP(x) => SCHEME_CHAR_STRINGP(x) SCHEME_STR_VAL(x) => SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(x)) SCHEME_STRLEN_VAL(x) => SCHEME_CHAR_STRLEN_VAL(x) scheme_make_string(x) => scheme_byte_string_to_char_string(scheme_make_byte_string(x)) */ /* See s-pg-types.h for data type constants. The file s-pg-types.h was manually constructed from the source mentioned. */ /* BUILD and USAGE on a FreeBSD box: mzc --cc ++ccf -Iinclude ++ccf -I/usr/local/include s-pg.c mzc ++ldf -L/usr/local/lib ++ldf -lpq --ld s-pg.so s-pg.o # mzscheme #> (load-extension "s-pg.so") > (require (lib "s-pg.ss" "spg")) > (require "s-pg.so") > (define lconnection (connection-open "dbname=mydbname user=myuser password=mypassword")) > (define lresult (result-open lconnection "SELECT * FROM mytable")) > (display (result-ref lresult 0)) > (result-close lresult) */ #include "escheme.h" #include "libpq-fe.h" #include "s-pg-types.h" #include "s-pg.h" enum action {GET_SCHEME_OBJECT, GET_METADATA}; Scheme_Object *sch_connection_open(int aargc, Scheme_Object **aargv) { PGconn *lconnection; if (!SCHEME_CHAR_STRINGP(aargv[0])) { scheme_wrong_type("connection-open", "string", 0, aargc, aargv); } lconnection = PQconnectdb(SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[0]))); if (PQstatus(lconnection) == CONNECTION_BAD) { //exn:scheme-pg:connection-open scheme_signal_error("%sCONNECTION INFOMATION: %s", PQerrorMessage(lconnection), SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[0]))); } return scheme_make_cptr(lconnection, "connection"); } Scheme_Object *sch_is_connection(int aargc, Scheme_Object **aargv) { PGconn *lconnection; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "connection")) { return scheme_false; } else { return scheme_true; } } Scheme_Object *sch_connection_close(int aargc, Scheme_Object **aargv) { PGconn *lconnection; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "connection")) { scheme_wrong_type("connection-close", "c-ptr:connection", 0, aargc, aargv); } else { lconnection = (PGconn *)SCHEME_CPTR_VAL(aargv[0]); } PQfinish(lconnection); return scheme_void; } Scheme_Object *sch_execute_sql(int aargc, Scheme_Object **aargv) { PGconn *lconnection; PGresult *lresult; long lnumber_rows_affected; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "connection")) { scheme_wrong_type("execute-sql", "c-ptr:connection", 0, aargc, aargv); } else { lconnection = (PGconn *)SCHEME_CPTR_VAL(aargv[0]); } if (!SCHEME_CHAR_STRINGP(aargv[1])) { scheme_wrong_type("execute-sql", "string", 1, aargc, aargv); } lresult = PQexec(lconnection, SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[1]))); if (PQresultStatus(lresult) != PGRES_COMMAND_OK) { if (PQresultStatus(lresult) == PGRES_TUPLES_OK) { //exn:scheme-pg:execute-sql:non-query scheme_signal_error("ERROR: execute-sql should not be used to retrieve data.\nSQL STATEMENT: %s", PQresultErrorMessage(lresult), SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[1]))); PQclear(lresult); } else { //exn:scheme-pg:execute-sql scheme_signal_error("%sSQL STATEMENT: %s", PQresultErrorMessage(lresult), SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[1]))); PQclear(lresult); } } lnumber_rows_affected = atol(PQcmdTuples(lresult)); PQclear(lresult); return scheme_make_integer(lnumber_rows_affected); } //creates an empty result /* Scheme_Object *sch_result_empty(int aargc, Scheme_Object **aargv) { PGconn *lconnection; PGresult *lresult; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "connection")) { scheme_wrong_type("result-open", "c-ptr:connection", 0, aargc, aargv); } else { lconnection = (PGconn *)SCHEME_CPTR_VAL(aargv[0]); } lresult = PQmakeEmptyPGresult(lconnection, NULL); return lresult; } */ Scheme_Object *sch_result_open(int aargc, Scheme_Object **aargv) { PGconn *lconnection; PGresult *lresult; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "connection")) { scheme_wrong_type("result-open", "c-ptr:connection", 0, aargc, aargv); } else { lconnection = (PGconn *)SCHEME_CPTR_VAL(aargv[0]); } if (!SCHEME_CHAR_STRINGP(aargv[1])) { scheme_wrong_type("result-open", "string", 1, aargc, aargv); } lresult = PQexec(lconnection, SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[1]))); if (PQresultStatus(lresult) != PGRES_TUPLES_OK) { //exn:scheme-pg:result-open scheme_signal_error("%sSQL STATEMENT: %s", PQresultErrorMessage(lresult), SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[1]))); PQclear(lresult); } return scheme_make_cptr(lresult, "result"); } Scheme_Object *sch_is_result(int aargc, Scheme_Object **aargv) { PGconn *lconnection; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "result")) { return scheme_false; } else { return scheme_true; } } Scheme_Object *sch_result_ref(int aargc, Scheme_Object **aargv) { PGresult *lresult; long lrow_number; int lcolumn_number; //lrow is an association list consisting of lpair's Scheme_Object *lrow = scheme_null; //lpair is an length two improper list, with the car being the field name as a symbol and the cadr the field value Scheme_Object *lpair; Scheme_Object *lvalue; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "result")) { scheme_wrong_type("result-ref", "c-ptr:result", 0, aargc, aargv); } else { lresult = (PGresult *)SCHEME_CPTR_VAL(aargv[0]); } if (!SCHEME_INTP(aargv[1])) { scheme_wrong_type("result-ref", "integer", 1, aargc, aargv); } else { lrow_number = SCHEME_INT_VAL(aargv[1]); } //note: this also raises an exception when the result is empty if (lrow_number < 0 || lrow_number > PQntuples(lresult)-1) { //exn:scheme-db:result-ref:index scheme_signal_error("index out of range"); } for (lcolumn_number = PQnfields(lresult)-1; lcolumn_number >= 0; lcolumn_number--) //notice the descreasing order { if (PQgetisnull(lresult, lrow_number, lcolumn_number)) { lvalue = scheme_intern_symbol("null-object"); } else { if (1==1) //TO DO: DO else option if user wants result column values returned as strings lvalue = sch_dbms_to_scheme(lresult, lrow_number, lcolumn_number, GET_SCHEME_OBJECT); else lvalue = scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(lresult, lrow_number, lcolumn_number))); } lpair = scheme_make_pair(scheme_intern_symbol(PQfname(lresult, lcolumn_number)), lvalue); lrow = scheme_make_pair(lpair, lrow); } return lrow; } Scheme_Object *sch_column_metadata(PGresult *aresult, int acolumn_number, char *atype) { // lmetadata is an association list Scheme_Object *lmetadata = scheme_null; Scheme_Object *lpair = scheme_null; lpair = scheme_make_pair(scheme_intern_symbol("type"), scheme_intern_symbol(atype)); lmetadata = scheme_make_pair(lpair, lmetadata); lpair = scheme_make_pair(scheme_intern_symbol("column"), scheme_intern_symbol(PQfname(aresult, acolumn_number))); lmetadata = scheme_make_pair(lpair, lmetadata); //Need access to a connection so can run the following SELECT to retrieve the actual table name //"SELECT relname FROM pg_class WHERE oid=%ld", PQftable(aresult, acolumn_number); lpair = scheme_make_pair(scheme_intern_symbol("table"), scheme_make_integer(PQftable(aresult, acolumn_number))); lmetadata = scheme_make_pair(lpair, lmetadata); return lmetadata; } // The value of argument aaction determines whether or not a Scheme_Object or metadata is returned Scheme_Object *sch_dbms_to_scheme(PGresult *aresult, long arow_number, int acolumn_number, int aaction) { switch (PQftype(aresult, acolumn_number)) { case BOOLOID: if (aaction == GET_SCHEME_OBJECT) if (strcmp(PQgetvalue(aresult, arow_number, acolumn_number), "t") == 0) return scheme_true; else if (strcmp(PQgetvalue(aresult, arow_number, acolumn_number), "f") == 0) return scheme_false; else //exn:scheme-db:conversion:boolean scheme_signal_error("Invalid boolean value in row %ld, column %d of result", arow_number, acolumn_number); else return sch_column_metadata(aresult, acolumn_number, "boolean"); // largest INT8OID is 9223372036854775807 case INT8OID: if (aaction == GET_SCHEME_OBJECT) return scheme_read_bignum_bytes(PQgetvalue(aresult, arow_number, acolumn_number), 0, 10); else return sch_column_metadata(aresult, acolumn_number, "bigint"); case VARCHAROID: if (aaction == GET_SCHEME_OBJECT) return scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "character-varying"); case DATEOID: if (aaction == GET_SCHEME_OBJECT) return scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "date"); case FLOAT8OID: if (aaction == GET_SCHEME_OBJECT) return scheme_make_double(atof(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "double-precision"); // also handles serial4 case INT4OID: if (aaction == GET_SCHEME_OBJECT) return scheme_make_integer_value(atol(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "integer"); case FLOAT4OID: if (aaction == GET_SCHEME_OBJECT) return scheme_make_float(atof(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "real"); case TEXTOID: if (aaction == GET_SCHEME_OBJECT) return scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "text"); case TIMEOID: if (aaction == GET_SCHEME_OBJECT) return scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "time-without-time-zone"); case TIMETZOID: if (aaction == GET_SCHEME_OBJECT) return scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "time-with-time-zone"); case TIMESTAMPOID: if (aaction == GET_SCHEME_OBJECT) return scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "timestamp-without-time-zone"); case TIMESTAMPTZOID: if (aaction == GET_SCHEME_OBJECT) return scheme_byte_string_to_char_string(scheme_make_byte_string(PQgetvalue(aresult, arow_number, acolumn_number))); else return sch_column_metadata(aresult, acolumn_number, "timestamp-with-time-zone"); default: //exn:scheme-db:conversion scheme_signal_error("Conversion of column %d of result with OID=%ld not handled", acolumn_number, PQftype(aresult, acolumn_number)); } } Scheme_Object *sch_result_metadata(int aargc, Scheme_Object **aargv) { PGresult *lresult; int i; Scheme_Object *lmetadata = scheme_null; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "result")) { scheme_wrong_type("sch_result_metadata", "c-ptr:result", 0, aargc, aargv); } else { lresult = (PGresult *)SCHEME_CPTR_VAL(aargv[0]); } for (i = 0; i < PQnfields(lresult); i++) { lmetadata = scheme_make_pair(sch_dbms_to_scheme(lresult, -1, i, GET_METADATA), lmetadata); } return lmetadata; } Scheme_Object *sch_result_length(int aargc, Scheme_Object **aargv) { PGresult *lresult; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "result")) { scheme_wrong_type("result-length", "c-ptr:result", 0, aargc, aargv); } else { lresult = (PGresult *)SCHEME_CPTR_VAL(aargv[0]); } return scheme_make_integer(PQntuples(lresult)); } Scheme_Object *sch_result_close(int aargc, Scheme_Object **aargv) { PGresult *lresult; if (!SCHEME_CPTRP(aargv[0]) || strcmp(SCHEME_CPTR_TYPE(aargv[0]), "result")) { scheme_wrong_type("result-close", "c-ptr:result", 0, aargc, aargv); } else { lresult = (PGresult *)SCHEME_CPTR_VAL(aargv[0]); } PQclear(lresult); return scheme_void; } Scheme_Object *sch_escape_string(int aargc, Scheme_Object **aargv) { char *lto; Scheme_Object *lstr; if (!SCHEME_CHAR_STRINGP(aargv[0])) { scheme_wrong_type("escape-string", "string", 1, aargc, aargv); } lto = malloc(2*strlen(SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[0])))*sizeof(char)+1); PQescapeString(lto, SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[0])), SCHEME_BYTE_STR_VAL(scheme_char_string_to_byte_string(aargv[0]))); lstr = scheme_byte_string_to_char_string(scheme_make_byte_string(lto)); free(lto); return(lstr); } Scheme_Object *sch_is_null_object(int aargc, Scheme_Object **aargv) { if (SCHEME_SYMBOLP(aargv[0]) && (strcmp(SCHEME_SYM_VAL(aargv[0]), "null-object") == 0)) { return scheme_true; } else { return scheme_false; } } Scheme_Object *scheme_initialize(Scheme_Env *env) { /* printf("sizeof(char)=%d\n", sizeof(char)); printf("sizeof(int)=%d\n", sizeof(int)); printf("sizeof(long)=%d\n", sizeof(long)); printf("sizeof(unsigned long)=%d\n", sizeof(unsigned long)); printf("sizeof(float)=%d\n", sizeof(float)); printf("sizeof(double)=%d\n", sizeof(double)); */ return scheme_reload(env); } Scheme_Object *scheme_reload(Scheme_Env *env) { Scheme_Object *proc; env = scheme_primitive_module(scheme_intern_symbol("s-pg"), env); proc = scheme_make_prim_w_arity(sch_connection_open, "connection-open", 1, 1); scheme_add_global("connection-open", proc, env); proc = scheme_make_prim_w_arity(sch_is_connection, "connection?", 1, 1); scheme_add_global("connection?", proc, env); proc = scheme_make_prim_w_arity(sch_connection_close, "connection-close", 1, 1); scheme_add_global("connection-close", proc, env); proc = scheme_make_prim_w_arity(sch_execute_sql, "execute-sql", 2, 2); scheme_add_global("execute-sql", proc, env); proc = scheme_make_prim_w_arity(sch_result_open, "result-open", 2, 2); scheme_add_global("result-open", proc, env); proc = scheme_make_prim_w_arity(sch_is_result, "result?", 1, 1); scheme_add_global("result?", proc, env); // proc = scheme_make_prim_w_arity(sch_result_empty, "result-empty", 1, 1); // scheme_add_global("result-empty", proc, env); proc = scheme_make_prim_w_arity(sch_result_ref, "result-ref", 2, 2); scheme_add_global("result-ref", proc, env); proc = scheme_make_prim_w_arity(sch_result_length, "result-length", 1, 1); scheme_add_global("result-length", proc, env); proc = scheme_make_prim_w_arity(sch_result_metadata, "result-metadata", 1, 1); scheme_add_global("result-metadata", proc, env); proc = scheme_make_prim_w_arity(sch_result_close, "result-close", 1, 1); scheme_add_global("result-close", proc, env); proc = scheme_make_prim_w_arity(sch_escape_string, "escape-string", 1, 1); scheme_add_global("escape-string", proc, env); proc = scheme_make_prim_w_arity(sch_is_null_object, "null-object?", 1, 1); scheme_add_global("null-object?", proc, env); scheme_finish_primitive_module(env); return scheme_void; } Scheme_Object *scheme_module_name() { return scheme_intern_symbol("s-pg"); }