
// // Copyright (c) 2008, John Sublett // Licensed under the Academic Free License version 3.0 // // History: // 12 Jan 08 John Sublett Creation // ** ** SqlService is the interface to a relational database. It is const ** and all state is stored as thread local variables. ** ** See `docLib::Sql`. ** const class SqlService : Thread { ** ** Make a new SqlService. ** ** - 'threadName' is the unique name used to identify the thread. ** - 'connection' is the connection string. For java this is the jdbc url. For .Net ** this is the connection string. ** - 'username' is the username for the database login. ** - 'password' is the password for the database login. ** - 'dialect' is the database specific dialect implementation. If null, ** MySqlDialect is assumed. ** new make(Str threadName := null, Str connection := "", Str username := "", Str password := "", Dialect dialect := null) : super(threadName) { this.connection = connection this.username = username this.password = password if (dialect == null) dialect = MySqlDialect.make else this.dialect = dialect } override SqlService start() { super.start return this } override protected Obj run() { log.info("SqlService started ($name)") log.info("SqlService connection: $connection") loop |Obj->Obj| { return null } log.info("SqlService stopped ($name)") return null } ** ** Open the database. This opens a connection to the database ** for the calling thread. A database must be open before ** it can be accessed. 'open' may be called multiple times. ** Each time 'open' is called, a counter is incremented. The ** database will not be closed until 'close' has been called ** for each call to 'open'. ** SqlService open() { if (!isRunning()) throw Err.make("SqlService not started") conn := (Connection)Thread.locals[name] if (conn == null) { conn = Connection.open(connection, username, password) Thread.locals[name] = conn } else { conn.increment } return this } ** ** Get the connection to this database for the current thread. ** private Connection threadConnection(Bool checked := true) { conn := (Connection)Thread.locals[name] if (conn == null) { if (checked) throw SqlErr.make("Database is not open.") else return null } if (conn.isClosed) { if (checked) { conn.close Thread.locals.remove(name) throw SqlErr.make("Database has been closed.") } else return null } return conn } ** ** Close the database. A call to 'close' may not actually ** close the database. It depends on how many times 'open' has ** been called for the current thread. The database will ** not be closed until 'close' has been called once for every time ** that 'open' has been called. ** Void close() { conn := (Connection)Thread.locals[name] if (conn != null) { if (conn.decrement == 0) { conn.close Thread.locals.remove(name) } } } ** ** Test the closed state of the database. ** Bool isClosed() { conn := threadConnection(false) if (conn == null) return true return conn.isClosed } ////////////////////////////////////////////////////////////////////////// // Metadata ////////////////////////////////////////////////////////////////////////// ** ** Does the specified table exist in the database? ** Bool tableExists(Str tableName) { return threadConnection.tableExists(tableName) } ** ** List the tables in the database. Returns a list of the ** table names. ** Str[] tables() { return threadConnection.tables(); } ** ** Get a default row instance for the specified table. The ** result has a field for each table column. ** Obj tableRow(Str tableName) { return threadConnection.tableRow(tableName) } ////////////////////////////////////////////////////////////////////////// // Statement ////////////////////////////////////////////////////////////////////////// ** ** Create a statement for this database. ** Statement sql(Str sql) { return threadConnection.sql(sql) } ** ** Get the key that was generated by the last statement ** or null if no key was generated. ** Int lastAutoKey() { return threadConnection.lastAutoKey } ////////////////////////////////////////////////////////////////////////// // Transactions ////////////////////////////////////////////////////////////////////////// ** ** Set the auto-commit state of this database. If true, each ** statement is committed as it is executed. If false, statements ** are grouped into transactions and committed when 'commit' ** Void autoCommit(Bool val) { threadConnection.autoCommit = val } ** ** Test the auto-commit state of this database. If true, each ** statement is committed as it is executed. If false, statements ** are grouped into transactions and committed when 'commit' ** Bool isAutoCommit() { return threadConnection.autoCommit } ** ** Commit all the changes made inside the current transaction. ** Void commit() { threadConnection.commit } ** ** Undo any changes made inside the current transaction. ** Void rollback() { threadConnection.rollback } ////////////////////////////////////////////////////////////////////////// // Fields ////////////////////////////////////////////////////////////////////////// ** Standard log for the sql service static const Log log := Log.get("sql") ** ** The database dialect for this service. ** const Dialect dialect ** ** The username used to connect to this database. ** const Str username ** ** The password used to connect to this database. ** const Str password ** ** The database specific string used to connect to the database. ** const Str connection }