diff --git a/libs/sql/execute.factor b/libs/sql/execute.factor new file mode 100644 index 0000000000..00ad16304d --- /dev/null +++ b/libs/sql/execute.factor @@ -0,0 +1,10 @@ +USING: kernel namespaces ; +IN: sql + +GENERIC: execute-sql* ( string db -- ) +GENERIC: query-sql* ( string db -- seq ) + +: execute-sql ( string -- ) db get execute-sql* ; +: query-sql ( string -- ) db get query-sql* ; + + diff --git a/libs/sql/load.factor b/libs/sql/load.factor new file mode 100644 index 0000000000..847cb5401b --- /dev/null +++ b/libs/sql/load.factor @@ -0,0 +1,27 @@ +PROVIDE: libs/sql +{ +files+ { + "sql.factor" + "utils.factor" + "simple.factor" + "mappings.factor" + "execute.factor" + + "sqlite/libsqlite.factor" + "sqlite/sqlite.factor" + "sqlite/simple.factor" + "sqlite/execute.factor" + "postgresql/libpq.factor" + "postgresql/postgresql.factor" + "postgresql/simple.factor" + "postgresql/execute.factor" + + "tupledb.factor" + + "thewebsite.factor" +} } +{ +tests+ { + "test/data.factor" + "test/insert.factor" + "test/util.factor" +} } ; + diff --git a/libs/sql/mappings.factor b/libs/sql/mappings.factor new file mode 100644 index 0000000000..e648a2ba8c --- /dev/null +++ b/libs/sql/mappings.factor @@ -0,0 +1 @@ +IN: sql diff --git a/libs/sql/postgresql/execute.factor b/libs/sql/postgresql/execute.factor new file mode 100644 index 0000000000..cff5fcbb2d --- /dev/null +++ b/libs/sql/postgresql/execute.factor @@ -0,0 +1 @@ +IN: postgresql diff --git a/libs/sql/postgresql/libpq.factor b/libs/sql/postgresql/libpq.factor new file mode 100644 index 0000000000..3786d1dc44 --- /dev/null +++ b/libs/sql/postgresql/libpq.factor @@ -0,0 +1,353 @@ +! See http://factor.sf.net/license.txt for BSD license. + +! adapted from libpq-fe.h version 7.4.7 +! tested on debian linux with postgresql 7.4.7 +! Updated to 8.1 + +IN: postgresql +USING: alien ; + + +! ConnSatusType +: CONNECTION_OK HEX: 0 ; inline +: CONNECTION_BAD HEX: 1 ; inline +: CONNECTION_STARTED HEX: 2 ; inline +: CONNECTION_MADE HEX: 3 ; inline +: CONNECTION_AWAITING_RESPONSE HEX: 4 ; inline +: CONNECTION_AUTH_OK HEX: 5 ; inline +: CONNECTION_SETENV HEX: 6 ; inline +: CONNECTION_SSL_STARTUP HEX: 7 ; inline +: CONNECTION_NEEDED HEX: 8 ; inline + +! PostgresPollingStatusType +: PGRES_POLLING_FAILED HEX: 0 ; inline +: PGRES_POLLING_READING HEX: 1 ; inline +: PGRES_POLLING_WRITING HEX: 2 ; inline +: PGRES_POLLING_OK HEX: 3 ; inline +: PGRES_POLLING_ACTIVE HEX: 4 ; inline + +! ExecStatusType; +: PGRES_EMPTY_QUERY HEX: 0 ; inline +: PGRES_COMMAND_OK HEX: 1 ; inline +: PGRES_TUPLES_OK HEX: 2 ; inline +: PGRES_COPY_OUT HEX: 3 ; inline +: PGRES_COPY_IN HEX: 4 ; inline +: PGRES_BAD_RESPONSE HEX: 5 ; inline +: PGRES_NONFATAL_ERROR HEX: 6 ; inline +: PGRES_FATAL_ERROR HEX: 7 ; inline + +! PGTransactionStatusType; +: PQTRANS_IDLE HEX: 0 ; inline +: PQTRANS_ACTIVE HEX: 1 ; inline +: PQTRANS_INTRANS HEX: 2 ; inline +: PQTRANS_INERROR HEX: 3 ; inline +: PQTRANS_UNKNOWN HEX: 4 ; inline + +! PGVerbosity; +: PQERRORS_TERSE HEX: 0 ; inline +: PQERRORS_DEFAULT HEX: 1 ; inline +: PQERRORS_VERBOSE HEX: 2 ; inline + + +TYPEDEF: int size_t +TYPEDEF: int ConnStatusType +TYPEDEF: int ExecStatusType +TYPEDEF: int PostgresPollingStatusType +TYPEDEF: int PGTransactionStatusType +TYPEDEF: int PGVerbosity + +TYPEDEF: void* PGconn* +TYPEDEF: void* PGresult* +TYPEDEF: void* PGcancel* +TYPEDEF: uint Oid +TYPEDEF: uint* Oid* +TYPEDEF: char pqbool +TYPEDEF: void* PQconninfoOption* +TYPEDEF: void* PGnotify* +TYPEDEF: void* PQArgBlock* +TYPEDEF: void* PQprintOpt* +TYPEDEF: void* FILE* +TYPEDEF: void* SSL* + +LIBRARY: postgresql + + +! Exported functions of libpq +! === in fe-connect.c === + +! make a new client connection to the backend +! Asynchronous (non-blocking) +FUNCTION: PGconn* PQconnectStart ( char* conninfo ) ; +FUNCTION: PostgresPollingStatusType PQconnectPoll ( PGconn* conn ) ; + +! Synchronous (blocking) +FUNCTION: PGconn* PQconnectdb ( char* conninfo ) ; +FUNCTION: PGconn* PQsetdbLogin ( char* pghost, char* pgport, + char* pgoptions, char* pgtty, + char* dbName, + char* login, char* pwd ) ; + +: PQsetdb ( M_PGHOST M_PGPORT M_PGOPT M_PGTTY M_DBNAME -- PGconn* ) + f f PQsetdbLogin ; + +! close the current connection and free the PGconn data structure +FUNCTION: void PQfinish ( PGconn* conn ) ; + +! get info about connection options known to PQconnectdb +FUNCTION: PQconninfoOption* PQconndefaults ( ) ; + +! free the data structure returned by PQconndefaults() +FUNCTION: void PQconninfoFree ( PQconninfoOption* connOptions ) ; + +! +! close the current connection and restablish a new one with the same +! parameters +! +! Asynchronous (non-blocking) +FUNCTION: int PQresetStart ( PGconn* conn ) ; +FUNCTION: PostgresPollingStatusType PQresetPoll ( PGconn* conn ) ; + +! Synchronous (blocking) +FUNCTION: void PQreset ( PGconn* conn ) ; + +! request a cancel structure +FUNCTION: PGcancel* PQgetCancel ( PGconn* conn ) ; + +! free a cancel structure +FUNCTION: void PQfreeCancel ( PGcancel* cancel ) ; + +! issue a cancel request +FUNCTION: int PQrequestCancel ( PGconn* conn ) ; + +! Accessor functions for PGconn objects +FUNCTION: char* PQdb ( PGconn* conn ) ; +FUNCTION: char* PQuser ( PGconn* conn ) ; +FUNCTION: char* PQpass ( PGconn* conn ) ; +FUNCTION: char* PQhost ( PGconn* conn ) ; +FUNCTION: char* PQport ( PGconn* conn ) ; +FUNCTION: char* PQtty ( PGconn* conn ) ; +FUNCTION: char* PQoptions ( PGconn* conn ) ; +FUNCTION: ConnStatusType PQstatus ( PGconn* conn ) ; +FUNCTION: PGTransactionStatusType PQtransactionStatus ( PGconn* conn ) ; +FUNCTION: char* PQparameterStatus ( PGconn* conn, + char* paramName ) ; +FUNCTION: int PQprotocolVersion ( PGconn* conn ) ; +FUNCTION: int PQServerVersion ( PGconn* conn ) ; +FUNCTION: char* PQerrorMessage ( PGconn* conn ) ; +FUNCTION: int PQsocket ( PGconn* conn ) ; +FUNCTION: int PQbackendPID ( PGconn* conn ) ; +FUNCTION: int PQclientEncoding ( PGconn* conn ) ; +FUNCTION: int PQsetClientEncoding ( PGconn* conn, char* encoding ) ; + +! May not be compiled into libpq +! Get the SSL structure associated with a connection +FUNCTION: SSL* PQgetssl ( PGconn* conn ) ; + +! Tell libpq whether it needs to initialize OpenSSL +FUNCTION: void PQinitSSL ( int do_init ) ; + +! Set verbosity for PQerrorMessage and PQresultErrorMessage +FUNCTION: PGVerbosity PQsetErrorVerbosity ( PGconn* conn, + PGVerbosity verbosity ) ; + +! Enable/disable tracing +FUNCTION: void PQtrace ( PGconn* conn, FILE* debug_port ) ; +FUNCTION: void PQuntrace ( PGconn* conn ) ; + +! BROKEN +! Function types for notice-handling callbacks +! typedef void (*PQnoticeReceiver) (void *arg, PGresult *res); +! typedef void (*PQnoticeProcessor) (void *arg, char* message); +! ALIAS: void* PQnoticeReceiver +! ALIAS: void* PQnoticeProcessor + +! Override default notice handling routines +! FUNCTION: PQnoticeReceiver PQsetNoticeReceiver ( PGconn* conn, + ! PQnoticeReceiver proc, + ! void* arg ) ; +! FUNCTION: PQnoticeProcessor PQsetNoticeProcessor ( PGconn* conn, + ! PQnoticeProcessor proc, + ! void* arg ) ; +! END BROKEN + +! === in fe-exec.c === + +! Simple synchronous query +FUNCTION: PGresult* PQexec ( PGconn* conn, char* query ) ; +FUNCTION: PGresult* PQexecParams ( PGconn* conn, + char* command, + int nParams, + Oid* paramTypes, + char** paramValues, + int* paramLengths, + int* paramFormats, + int resultFormat ) ; +FUNCTION: PGresult* PQprepare ( PGconn* conn, char* stmtName, + char* query, int nParams, + Oid* paramTypes ) ; +FUNCTION: PGresult* PQexecPrepared ( PGconn* conn, + char* stmtName, + int nParams, + char** paramValues, + int* paramLengths, + int* paramFormats, + int resultFormat ) ; + +! Interface for multiple-result or asynchronous queries +FUNCTION: int PQsendQuery ( PGconn* conn, char* query ) ; +FUNCTION: int PQsendQueryParams ( PGconn* conn, + char* command, + int nParams, + Oid* paramTypes, + char** paramValues, + int* paramLengths, + int* paramFormats, + int resultFormat ) ; +FUNCTION: PGresult* PQsendPrepare ( PGconn* conn, char* stmtName, + char* query, int nParams, + Oid* paramTypes ) ; +FUNCTION: int PQsendQueryPrepared ( PGconn* conn, + char* stmtName, + int nParams, + char** paramValues, + int *paramLengths, + int *paramFormats, + int resultFormat ) ; +FUNCTION: PGresult* PQgetResult ( PGconn* conn ) ; + +! Routines for managing an asynchronous query +FUNCTION: int PQisBusy ( PGconn* conn ) ; +FUNCTION: int PQconsumeInput ( PGconn* conn ) ; + +! LISTEN/NOTIFY support +FUNCTION: PGnotify* PQnotifies ( PGconn* conn ) ; + +! Routines for copy in/out +FUNCTION: int PQputCopyData ( PGconn* conn, char* buffer, int nbytes ) ; +FUNCTION: int PQputCopyEnd ( PGconn* conn, char* errormsg ) ; +FUNCTION: int PQgetCopyData ( PGconn* conn, char** buffer, int async ) ; + +! Deprecated routines for copy in/out +FUNCTION: int PQgetline ( PGconn* conn, char* string, int length ) ; +FUNCTION: int PQputline ( PGconn* conn, char* string ) ; +FUNCTION: int PQgetlineAsync ( PGconn* conn, char* buffer, int bufsize ) ; +FUNCTION: int PQputnbytes ( PGconn* conn, char* buffer, int nbytes ) ; +FUNCTION: int PQendcopy ( PGconn* conn ) ; + +! Set blocking/nonblocking connection to the backend +FUNCTION: int PQsetnonblocking ( PGconn* conn, int arg ) ; +FUNCTION: int PQisnonblocking ( PGconn* conn ) ; + +! Force the write buffer to be written (or at least try) +FUNCTION: int PQflush ( PGconn* conn ) ; + +! +! * "Fast path" interface --- not really recommended for application +! * use +! +FUNCTION: PGresult* PQfn ( PGconn* conn, + int fnid, + int* result_buf, + int* result_len, + int result_is_int, + PQArgBlock* args, + int nargs ) ; + +! Accessor functions for PGresult objects +FUNCTION: ExecStatusType PQresultStatus ( PGresult* res ) ; +FUNCTION: char* PQresStatus ( ExecStatusType status ) ; +FUNCTION: char* PQresultErrorMessage ( PGresult* res ) ; +FUNCTION: char* PQresultErrorField ( PGresult* res, int fieldcode ) ; +FUNCTION: int PQntuples ( PGresult* res ) ; +FUNCTION: int PQnfields ( PGresult* res ) ; +FUNCTION: int PQbinaryTuples ( PGresult* res ) ; +FUNCTION: char* PQfname ( PGresult* res, int field_num ) ; +FUNCTION: int PQfnumber ( PGresult* res, char* field_name ) ; +FUNCTION: Oid PQftable ( PGresult* res, int field_num ) ; +FUNCTION: int PQftablecol ( PGresult* res, int field_num ) ; +FUNCTION: int PQfformat ( PGresult* res, int field_num ) ; +FUNCTION: Oid PQftype ( PGresult* res, int field_num ) ; +FUNCTION: int PQfsize ( PGresult* res, int field_num ) ; +FUNCTION: int PQfmod ( PGresult* res, int field_num ) ; +FUNCTION: char* PQcmdStatus ( PGresult* res ) ; +FUNCTION: char* PQoidStatus ( PGresult* res ) ; +FUNCTION: Oid PQoidValue ( PGresult* res ) ; +FUNCTION: char* PQcmdTuples ( PGresult* res ) ; +FUNCTION: char* PQgetvalue ( PGresult* res, int tup_num, int field_num ) ; +FUNCTION: int PQgetlength ( PGresult* res, int tup_num, int field_num ) ; +FUNCTION: int PQgetisnull ( PGresult* res, int tup_num, int field_num ) ; + +! Delete a PGresult +FUNCTION: void PQclear ( PGresult* res ) ; + +! For freeing other alloc'd results, such as PGnotify structs +FUNCTION: void PQfreemem ( void* ptr ) ; + +! Exists for backward compatibility. +: PQfreeNotify PQfreemem ; + +! +! Make an empty PGresult with given status (some apps find this +! useful). If conn is not NULL and status indicates an error, the +! conn's errorMessage is copied. +! +FUNCTION: PGresult* PQmakeEmptyPGresult ( PGconn* conn, ExecStatusType status ) ; + +! Quoting strings before inclusion in queries. +FUNCTION: size_t PQescapeStringConn ( PGconn* conn, + char* to, char* from, size_t length, + int* error ) ; +FUNCTION: uchar* PQescapeByteaConn ( PGconn* conn, + char* from, size_t length, + size_t* to_length ) ; +FUNCTION: uchar* PQunescapeBytea ( uchar* strtext, + size_t* retbuflen ) ; +! These forms are deprecated! +FUNCTION: size_t PQescapeString ( void* to, char* from, size_t length ) ; +FUNCTION: uchar* PQescapeBytea ( uchar* bintext, size_t binlen, + size_t* bytealen ) ; + +! === in fe-print.c === + +FUNCTION: void PQprint ( FILE* fout, PGresult* res, PQprintOpt* ps ) ; + +! really old printing routines +FUNCTION: void PQdisplayTuples ( PGresult* res, + FILE* fp, + int fillAlign, + char* fieldSep, + int printHeader, + int quiet ) ; + +FUNCTION: void PQprintTuples ( PGresult* res, + FILE* fout, + int printAttName, + int terseOutput, + int width ) ; + +! === in fe-lobj.c === + +! Large-object access routines +FUNCTION: int lo_open ( PGconn* conn, Oid lobjId, int mode ) ; +FUNCTION: int lo_close ( PGconn* conn, int fd ) ; +FUNCTION: int lo_read ( PGconn* conn, int fd, char* buf, size_t len ) ; +FUNCTION: int lo_write ( PGconn* conn, int fd, char* buf, size_t len ) ; +FUNCTION: int lo_lseek ( PGconn* conn, int fd, int offset, int whence ) ; +FUNCTION: Oid lo_creat ( PGconn* conn, int mode ) ; +FUNCTION: Oid lo_creat ( PGconn* conn, Oid lobjId ) ; +FUNCTION: int lo_tell ( PGconn* conn, int fd ) ; +FUNCTION: int lo_unlink ( PGconn* conn, Oid lobjId ) ; +FUNCTION: Oid lo_import ( PGconn* conn, char* filename ) ; +FUNCTION: int lo_export ( PGconn* conn, Oid lobjId, char* filename ) ; + +! === in fe-misc.c === + +! Determine length of multibyte encoded char at *s +FUNCTION: int PQmblen ( uchar* s, int encoding ) ; + +! Determine display length of multibyte encoded char at *s +FUNCTION: int PQdsplen ( uchar* s, int encoding ) ; + +! Get encoding id from environment variable PGCLIENTENCODING +FUNCTION: int PQenv2encoding ( ) ; + diff --git a/libs/sql/postgresql/postgresql.factor b/libs/sql/postgresql/postgresql.factor new file mode 100644 index 0000000000..54e73ccaf8 --- /dev/null +++ b/libs/sql/postgresql/postgresql.factor @@ -0,0 +1,60 @@ +! See http://factor.sf.net/license.txt for BSD license. + +! adapted from libpq-fe.h version 7.4.7 +! tested on debian linux with postgresql 7.4.7 + +IN: postgresql +USING: kernel alien errors io prettyprint sequences namespaces arrays math sql ; + +SYMBOL: query-res + +: connect-postgres ( host port pgopts pgtty db user pass -- conn ) + PQsetdbLogin + dup PQstatus zero? [ "couldn't connect to database" throw ] unless ; + +: with-postgres ( host port pgopts pgtty db user pass quot -- ) + [ >r connect-postgres db set r> + [ db get PQfinish ] cleanup ] with-scope ; inline + +: with-postgres-catch ( host port pgopts pgtty db user pass quot -- ) + [ with-postgres ] catch [ "caught: " write print ] when* ; + +: postgres-error ( ret -- ret ) + dup zero? [ PQresultErrorMessage throw ] when ; + +: (do-query) ( PGconn query -- PGresult* ) + ! For queries that do not return rows, PQexec() returns PGRES_COMMAND_OK + ! For queries that return rows, PQexec() returns PGRES_TUPLES_OK + PQexec + dup PQresultStatus PGRES_COMMAND_OK = + over PQresultStatus PGRES_TUPLES_OK = + or [ + [ PQresultErrorMessage CHAR: \n swap remove ] keep PQclear throw + ] unless ; + +: (do-command) ( PGconn query -- PGresult* ) + [ (do-query) ] catch + [ + swap + "non-fatal error: " print + "\tQuery: " write "'" write write "'" print + "\t" write print + ] when* drop ; + +: do-command ( str -- ) + unit \ (do-command) add db get swap call ; + +: prepare ( str quot word -- conn quot ) + rot unit swap append swap append db get swap ; + +: do-query ( str quot -- ) + [ (do-query) query-res set ] prepare catch + [ rethrow ] [ query-res get PQclear ] if* ; + +: result>seq ( -- seq ) + query-res get [ PQnfields ] keep PQntuples + [ swap [ query-res get -rot PQgetvalue ] map-with ] map-with ; + +: print-table ( seq -- ) + [ [ write bl ] each "\n" write ] each ; + diff --git a/libs/sql/postgresql/simple.factor b/libs/sql/postgresql/simple.factor new file mode 100644 index 0000000000..cff5fcbb2d --- /dev/null +++ b/libs/sql/postgresql/simple.factor @@ -0,0 +1 @@ +IN: postgresql diff --git a/libs/sql/simple-bind.factor b/libs/sql/simple-bind.factor new file mode 100644 index 0000000000..124fb86f40 --- /dev/null +++ b/libs/sql/simple-bind.factor @@ -0,0 +1,4 @@ +USING: kernel ; +IN: sql + + diff --git a/libs/sql/simple.factor b/libs/sql/simple.factor new file mode 100644 index 0000000000..10a083f745 --- /dev/null +++ b/libs/sql/simple.factor @@ -0,0 +1,52 @@ +USING: generic kernel namespaces prettyprint sequences sql:utils ; +IN: sql + +GENERIC: create-sql* ( tuple db -- string ) +GENERIC: drop-sql* ( tuple db -- string ) +GENERIC: insert-sql* ( tuple db -- string ) +GENERIC: delete-sql* ( tuple db -- string ) +GENERIC: update-sql* ( tuple db -- string ) +GENERIC: select-sql* ( tuple db -- string ) + +: create-sql ( tuple -- string ) db get create-sql* ; +: drop-sql ( tuple -- string ) db get drop-sql* ; +: insert-sql ( tuple -- string ) db get insert-sql* ; +: delete-sql ( tuple -- string ) db get delete-sql* ; +: update-sql ( tuple -- string ) db get update-sql* ; +: select-sql ( tuple -- string ) db get select-sql* ; + +M: connection create-sql* ( tuple db -- string ) + drop [ + "create table " % + dup class unparse % "(" % + tuple>mapping% + ");" % + ] "" make ; + +M: connection drop-sql* ( tuple db -- string ) + drop [ "drop table " % tuple>sql-name % ";" % ] "" make ; + +M: connection insert-sql* ( tuple db -- string ) + drop [ + "insert into " % + dup tuple>sql-name % + " (" % tuple>insert-parts dup first ", " join % + ") values(" % + second [ escape-sql enquote ] map ", " join % + ");" % + ] "" make ; + +M: connection delete-sql* ( tuple db -- string ) + drop [ + ! "delete from table " % unparse % ";" % + ] "" make ; + +M: connection update-sql* ( tuples db -- string ) + drop [ + ] "" make ; + +M: connection select-sql* ( tuples db -- string ) + drop [ + ] "" make ; + + diff --git a/libs/sql/sql.factor b/libs/sql/sql.factor new file mode 100644 index 0000000000..677993983a --- /dev/null +++ b/libs/sql/sql.factor @@ -0,0 +1,10 @@ +USING: kernel namespaces ; +IN: sql + +SYMBOL: db +TUPLE: connection handle ; + +! TESTING +"handle" db set-global + + diff --git a/libs/sql/sqlite/execute.factor b/libs/sql/sqlite/execute.factor new file mode 100644 index 0000000000..f75342a1c5 --- /dev/null +++ b/libs/sql/sqlite/execute.factor @@ -0,0 +1,7 @@ +USING: kernel namespaces sql ; +IN: sqlite + +M: sqlite execute-sql* ( string db -- ) + connection-handle swap + sqlite-prepare dup [ drop ] sqlite-each sqlite-finalize ; + diff --git a/libs/sql/sqlite/libsqlite.factor b/libs/sql/sqlite/libsqlite.factor new file mode 100644 index 0000000000..75f3cd6b90 --- /dev/null +++ b/libs/sql/sqlite/libsqlite.factor @@ -0,0 +1,111 @@ +! Copyright (C) 2005 Chris Double, Doug Coleman. +! See http://factorcode.org/license.txt for BSD license. +! +! An interface to the sqlite database. Tested against sqlite v3.1.3. +! Remeber to pass the following to factor: +! -libraries:sqlite=libsqlite3.so +! +! Not all functions have been wrapped yet. Only those directly involving +! executing SQL calls and obtaining results. +! +IN: libsqlite +USING: alien compiler errors kernel math namespaces sequences strings ; + +! Return values from sqlite functions +: SQLITE_OK 0 ; inline ! Successful result +: SQLITE_ERROR 1 ; inline ! SQL error or missing database +: SQLITE_INTERNAL 2 ; inline ! An internal logic error in SQLite +: SQLITE_PERM 3 ; inline ! Access permission denied +: SQLITE_ABORT 4 ; inline ! Callback routine requested an abort +: SQLITE_BUSY 5 ; inline ! The database file is locked +: SQLITE_LOCKED 6 ; inline ! A table in the database is locked +: SQLITE_NOMEM 7 ; inline ! A malloc() failed +: SQLITE_READONLY 8 ; inline ! Attempt to write a readonly database +: SQLITE_INTERRUPT 9 ; inline ! Operation terminated by sqlite_interrupt() +: SQLITE_IOERR 10 ; inline ! Some kind of disk I/O error occurred +: SQLITE_CORRUPT 11 ; inline ! The database disk image is malformed +: SQLITE_NOTFOUND 12 ; inline ! (Internal Only) Table or record not found +: SQLITE_FULL 13 ; inline ! Insertion failed because database is full +: SQLITE_CANTOPEN 14 ; inline ! Unable to open the database file +: SQLITE_PROTOCOL 15 ; inline ! Database lock protocol error +: SQLITE_EMPTY 16 ; inline ! (Internal Only) Database table is empty +: SQLITE_SCHEMA 17 ; inline ! The database schema changed +: SQLITE_TOOBIG 18 ; inline ! Too much data for one row of a table +: SQLITE_CONSTRAINT 19 ; inline ! Abort due to contraint violation +: SQLITE_MISMATCH 20 ; inline ! Data type mismatch +: SQLITE_MISUSE 21 ; inline ! Library used incorrectly +: SQLITE_NOLFS 22 ; inline ! Uses OS features not supported on host +: SQLITE_AUTH 23 ; inline ! Authorization denied +: SQLITE_FORMAT 24 ; inline ! Auxiliary database format error +: SQLITE_RANGE 25 ; inline ! 2nd parameter to sqlite3_bind out of range +: SQLITE_NOTADB 26 ; inline ! File opened that is not a database file + +: sqlite-error-messages ( -- seq ) { + "Successful result" + "SQL error or missing database" + "An internal logic error in SQLite" + "Access permission denied" + "Callback routine requested an abort" + "The database file is locked" + "A table in the database is locked" + "A malloc() failed" + "Attempt to write a readonly database" + "Operation terminated by sqlite_interrupt()" + "Some kind of disk I/O error occurred" + "The database disk image is malformed" + "(Internal Only) Table or record not found" + "Insertion failed because database is full" + "Unable to open the database file" + "Database lock protocol error" + "(Internal Only) Database table is empty" + "The database schema changed" + "Too much data for one row of a table" + "Abort due to contraint violation" + "Data type mismatch" + "Library used incorrectly" + "Uses OS features not supported on host" + "Authorization denied" + "Auxiliary database format error" + "2nd parameter to sqlite3_bind out of range" + "File opened that is not a database file" +} ; + +: SQLITE_ROW 100 ; inline ! sqlite_step() has another row ready +: SQLITE_DONE 101 ; inline ! sqlite_step() has finished executing + +! Return values from the sqlite3_column_type function +: SQLITE_INTEGER 1 ; inline +: SQLITE_FLOAT 2 ; inline +: SQLITE_TEXT 3 ; inline +: SQLITE_BLOB 4 ; inline +: SQLITE_NULL 5 ; inline + +! Values for the 'destructor' parameter of the 'bind' routines. +: SQLITE_STATIC 0 ; inline +: SQLITE_TRANSIENT -1 ; inline + +TYPEDEF: void sqlite3 +TYPEDEF: void sqlite3_stmt + +LIBRARY: sqlite +FUNCTION: int sqlite3_open ( char* filename, void* ppDb ) ; +FUNCTION: int sqlite3_close ( sqlite3* pDb ) ; +FUNCTION: int sqlite3_prepare ( sqlite3* pDb, char* zSql, int nBytes, void* ppStmt, void* pzTail ) ; +FUNCTION: int sqlite3_finalize ( sqlite3_stmt* pStmt ) ; +FUNCTION: int sqlite3_reset ( sqlite3_stmt* pStmt ) ; +FUNCTION: int sqlite3_step ( sqlite3_stmt* pStmt ) ; +FUNCTION: int sqlite3_last_insert_rowid ( sqlite3* pStmt ) ; +FUNCTION: int sqlite3_bind_blob ( sqlite3_stmt* pStmt, int index, void* ptr, int len, int destructor ) ; +FUNCTION: int sqlite3_bind_int ( sqlite3_stmt* pStmt, int index, int n ) ; +FUNCTION: int sqlite3_bind_null ( sqlite3_stmt* pStmt, int n ) ; +FUNCTION: int sqlite3_bind_text ( sqlite3_stmt* pStmt, int index, char* text, int len, int destructor ) ; +FUNCTION: int sqlite3_bind_parameter_index ( sqlite3_stmt* pStmt, char* name ) ; +FUNCTION: int sqlite3_column_count ( sqlite3_stmt* pStmt ) ; +FUNCTION: void* sqlite3_column_blob ( sqlite3_stmt* pStmt, int col ) ; +FUNCTION: int sqlite3_column_bytes ( sqlite3_stmt* pStmt, int col ) ; +FUNCTION: char* sqlite3_column_decltype ( sqlite3_stmt* pStmt, int col ) ; +FUNCTION: int sqlite3_column_int ( sqlite3_stmt* pStmt, int col ) ; +FUNCTION: int sqlite3_column_name ( sqlite3_stmt* pStmt, int col ) ; +FUNCTION: char* sqlite3_column_text ( sqlite3_stmt* pStmt, int col ) ; +FUNCTION: int sqlite3_column_type ( sqlite3_stmt* pStmt, int col ) ; + diff --git a/libs/sql/sqlite/load.factor b/libs/sql/sqlite/load.factor new file mode 100644 index 0000000000..9818e3bc37 --- /dev/null +++ b/libs/sql/sqlite/load.factor @@ -0,0 +1,11 @@ +PROVIDE: libs/sqlite +{ +files+ { + "libsqlite.factor" + "sqlite.factor" + "sqlite.facts" + "tuple-db.factor" + "tuple-db.facts" +} } +{ +tests+ { + "tuple-db-tests.factor" +} } ; diff --git a/libs/sql/sqlite/simple.factor b/libs/sql/sqlite/simple.factor new file mode 100644 index 0000000000..9c45c17608 --- /dev/null +++ b/libs/sql/sqlite/simple.factor @@ -0,0 +1,24 @@ +USING: generic kernel namespaces prettyprint sql sql:utils ; +IN: sqlite + +TUPLE: sqlite ; +C: sqlite ( path -- db ) + >r sqlite-open r> + [ set-delegate ] keep ; + +! M: sqlite insert-sql* ( tuple db -- string ) + #! Insert and fill in the ID column + ! ; + +M: sqlite delete-sql* ( tuple db -- string ) + #! Delete based on the ID column + ; + +M: sqlite update-sql* ( tuple db -- string ) + #! Update based on the ID column + ; + +M: sqlite select-sql* ( tuple db -- string ) + ; + + diff --git a/libs/sql/sqlite/sqlite.factor b/libs/sql/sqlite/sqlite.factor new file mode 100644 index 0000000000..a5b603a949 --- /dev/null +++ b/libs/sql/sqlite/sqlite.factor @@ -0,0 +1,126 @@ +! Copyright (C) 2005 Chris Double. +! See http://factorcode.org/license.txt for BSD license. +! +! An interface to the sqlite database. Tested against sqlite v3.0.8. +! Remeber to pass the following to factor: +! -libraries:sqlite=libsqlite3.so +! +! Not all functions have been wrapped yet. Only those directly involving +! executing SQL calls and obtaining results. +! +IN: sqlite +USING: alien compiler errors libsqlite kernel namespaces sequences sql strings ; + +TUPLE: sqlite-error n message ; + +! High level sqlite routines +: sqlite-check-result ( result -- ) + #! Check the result from a sqlite call is ok. If it is + #! return, otherwise throw an error. + dup SQLITE_OK = [ + drop + ] [ + dup sqlite-error-messages nth throw + ] if ; + +: sqlite-open ( filename -- db ) + #! Open the database referenced by the filename and return + #! a handle to that database. An error is thrown if the database + #! failed to open. + "void*" [ sqlite3_open sqlite-check-result ] keep *void* ; + +: sqlite-close ( db -- ) + #! Close the given database + sqlite3_close sqlite-check-result ; + +: sqlite-last-insert-rowid ( db -- rowid ) + #! Return the rowid of the last insert + sqlite3_last_insert_rowid ; + +: sqlite-prepare ( db sql -- statement ) + #! Prepare a SQL statement. Returns the statement which + #! can have values bound to parameters or simply executed. + #! TODO: Support multiple statements in the SQL string. + dup length "void*" "void*" + [ sqlite3_prepare sqlite-check-result ] 2keep + drop *void* ; + +: sqlite-bind-text ( statement index text -- ) + #! Bind the text to the parameterized value in the statement. + dup length SQLITE_TRANSIENT sqlite3_bind_text sqlite-check-result ; + +: sqlite-bind-parameter-index ( statement name -- index ) + sqlite3_bind_parameter_index ; + +: sqlite-bind-text-by-name ( statement name text -- ) + >r dupd sqlite-bind-parameter-index r> sqlite-bind-text ; + +: sqlite-finalize ( statement -- ) + #! Clean up all resources related to a statement. Once called + #! the statement cannot be used. All statements must be finalized + #! before closing the database. + sqlite3_finalize sqlite-check-result ; + +: sqlite-reset ( statement -- ) + #! Reset a statement so it can be called again, possibly with + #! different parameters. + sqlite3_reset sqlite-check-result ; + +: column-count ( statement -- int ) + #! Given a prepared statement, return the number of + #! columns in each row of the result set of that statement. + sqlite3_column_count ; + +: column-text ( statement index -- string ) + #! Return the value of the given column, indexed + #! from zero, as a string. + sqlite3_column_text ; + +: step-complete? ( step-result -- bool ) + #! Return true if the result of a sqlite3_step is + #! such that the iteration has completed (ie. it is + #! SQLITE_DONE). Throw an error if an error occurs. + dup SQLITE_ROW = [ + drop f + ] [ + dup SQLITE_DONE = [ + drop t + ] [ + sqlite-check-result t + ] if + ] if ; + +: sqlite-each ( statement quot -- ) + #! Execute the SQL statement, and call the quotation for + #! each row returned from executing the statement with the + #! statement on the top of the stack. + over sqlite3_step step-complete? [ + 2drop + ] [ + [ call ] 2keep sqlite-each + ] if ; inline + +! For comparison, here is the linrec implementation of sqlite-each +! [ drop sqlite3_step step-complete? ] +! [ 2drop ] +! [ 2dup 2slip ] +! [ ] linrec ; + +DEFER: (sqlite-map) + +: (sqlite-map) ( statement quot seq -- ) + pick sqlite3_step step-complete? [ + 2nip + ] [ + >r 2dup call r> curry (sqlite-map) + ] if ; + +: sqlite-map ( statement quot -- seq ) + [ ] (sqlite-map) ; + +: with-sqlite ( path quot -- ) + [ + >r sqlite-open db set r> + [ db get sqlite-close ] cleanup + ] with-scope ; + diff --git a/libs/sql/tupledb.factor b/libs/sql/tupledb.factor new file mode 100644 index 0000000000..67276e1764 --- /dev/null +++ b/libs/sql/tupledb.factor @@ -0,0 +1,14 @@ +USING: kernel math sql:utils ; +IN: sql + +: save ( tuple -- ) + dup "id" tuple-slot [ + ! update + ] [ + ! insert + ] if ; + +: restore ( tuple -- ) + ; + + diff --git a/libs/sql/utils.factor b/libs/sql/utils.factor new file mode 100644 index 0000000000..dd7efcf92b --- /dev/null +++ b/libs/sql/utils.factor @@ -0,0 +1,151 @@ +USING: arrays errors generic hashtables kernel math namespaces +prettyprint sequences sql strings tools words ; +IN: sql:utils + +! : 2seq>hash 2array flip alist>hash ; + +: 2seq>hash ( seq seq -- hash ) + H{ } clone -rot [ pick set-hash ] 2each ; + +: tuple-fields ( tuple -- seq ) + class "slot-names" word-prop ; + +: tuple>parts ( tuple -- values names ) + [ tuple-slots ] keep tuple-fields ; + +: tuple>hash ( tuple -- hash ) + tuple>parts 2seq>hash ; + +: tuple>all-slots + delegates V{ } clone + [ tuple-slots dupd nappend ] reduce + prune >array ; + +: tuple>all-fields + delegates V{ } clone + [ tuple-fields dupd nappend ] reduce + prune >array ; + +: full-tuple>hash ( tuple -- hash ) + delegates + H{ } clone [ tuple>hash hash-union ] reduce ; + +: tuple>all-parts ( tuple -- values names ) + [ + [ full-tuple>hash ] keep tuple>all-fields + [ swap hash ] map-with + ] keep tuple>all-fields ; + +: maybe-unparse ( obj -- ) + dup string? [ unparse ] unless ; + +: replace ( new old seq -- seq ) + >r 2seq>hash r> [ + [ + [ + tuck swap hash* [ nip ] [ drop ] if + dup sequence? [ % ] [ , ] if + ] each-with + ] { } make + ] keep like ; + +GENERIC: escape-sql* ( string type db -- string ) + +M: connection escape-sql* ( string type db -- string ) + drop { "''" } "'" rot replace ; + +: escape-sql ( string type -- string ) db get escape-sql* ; + +: sanitize-name ( string -- string ) + "_p" "-?" pick subst ; + +: tuple>sql-name ( tuple -- string ) + class unparse sanitize-name ; + +: enquote% "'" % % "'" % ; + +: enquote ( string -- 'string' ) + [ enquote% ] "" make ; + +: split-last ( seq -- last most ) + dup length { + { [ dup zero? ] [ 2drop f f ] } + { [ dup 1 = ] [ drop f ] } + { [ t ] [ >r [ peek 1array ] keep r> 1- head ] } + } cond ; + +: (each-last) ( seq quot quot -- ) + >r >r split-last r> each r> each ; inline + +: each-last ( seq quot quot -- ) + >r dup clone r> append swap (each-last) ; + +: (2each-last) ( seq seq quot quot -- ) + >r >r [ split-last ] 2apply swapd r> 2each r> 2each ; inline + +: 2each-last ( seq seq quot quot -- ) + #! apply first quotation on all but last elt of seq + #! apply second quotation on last element + >r dup clone r> append swap (2each-last) ; + +! { integer string } +! mapping: { integer { varchar(256) "not null" } } +! { "a integer" "b string" } + +SYMBOL: mappings +H{ } clone mappings set-global + +: get-mapping ( tuple -- seq ) + dup class mappings get hash* [ + nip + ] [ + drop tuple-slots [ drop "varchar" ] map + ] if ; + +: tuple>mapping% ( obj -- seq ) + [ get-mapping ] keep tuple-fields + [ sanitize-name % " " % % ] [ ", " % ] 2each-last ; + +: tuple>mapping ( tuple -- string ) + [ tuple>mapping% ] "" make ; + +: tuple>insert-parts ( tuple -- string ) + [ + tuple>parts + [ + dup "id" = [ + 2drop + ] [ + over [ swap 2array , ] [ 2drop ] if + ] if + ] 2each + ] { } make flip ; + +: tuple>assignments% ( tuple -- string ) + [ tuple-slots [ maybe-unparse escape-sql ] map ] keep + tuple-fields + [ sanitize-name % " = " % enquote% ] [ ", " % ] 2each-last ; + +: tuple>assignments% ( tuple -- string ) + tuple>parts dup [ "id" = ] find drop + dup -1 = [ "tuple must have an id slot" throw ] when + swap >r tuck >r remove-nth r> r> remove-nth + >r [ maybe-unparse escape-sql ] map r> + [ % " = " % enquote% ] [ ", " % ] 2each-last ; + +: tuple>assignments ( tuple -- string ) + [ tuple>assignments% ] "" make ; + +: tuple-slot ( string slot -- ? obj ) + "slot-names" over class word-props hash + rot [ = ] curry find over -1 = [ + swap + ] [ + drop 2 + swap tuple>array nth >r t r> + ] if ; + +: explode-tuple ( tuple -- ) + dup tuple-slots swap class "slot-names" word-prop + [ set ] 2each ; + +