pod
sql
Relational database access
mixins
SqlConn |
SqlConn manages a connection to a relational database. |
---|
classes
Col |
Col models a column of a relational table. |
---|---|
Row |
Row models a row of a relational table. |
SqlConnPool |
SqlConnPool manages a pool of reusable SQL connections |
SqlMeta |
SqlMeta provides access to database meta-data |
Statement |
Statement is an executable statement for a specific database. |
errs
SqlErr |
SqlErr indicates an error from the SQL database driver. |
---|
docs
Overview
The sql pod provides a standardized, low level API to work with relational databases. Its basic goals in life:
- Connections: manage interaction with the database
- Statements: execute SQL statements
- Model: model meta-data and relational tables
Test Setup
The sql tests use the following config props to determine the database configuration:
test.uri
: connection/jdbc url string, defaults to "jdbc:mysql://localhost:3306/fantest"test.username
: defaults to "fantest"test.password
: defaults to "fantest"
You can change these defaults via "etc/sql/config.props".
We've been testing with MySql 5.0.41 running InnoDB using "fantest" for the database name, user name, and password. You can configure InnoDB as the default storage engine with this line in your "my.cfg":
default-storage-engine=INNODB
To setup the fantest database and user account via the mysql command line:
mysql -u root -p mysql> create user fantest identified by 'fantest'; mysql> create database fantest; mysql> grant all privileges on fantest.* to fantest;
To use Postgres as the test database, modify "etc/sql/config.props" so that test.uri
equals "jdbc:postgresql://localhost:5432/postgres", and then setup the fantest database and user account via the Postgres command line:
sudo -u postgres psql postgres=# create role fantest with login superuser password 'fantest'; postgres=# alter role fantest with login; postgres=# create schema authorization fantest;
Connections
Connections are managed by the SqlConn
class. To open and close connections to the database, simply call the SqlConn.open
and SqlConn.close
methods.
Connections in Java
When running in a Java VM, Fantom uses JDBC under the covers. Using MySQL as an example, follow these steps to open a connection in the JVM:
- Ensure your JDBC driver is installed and available via the system class path. The best place to stick it is in the "jre/lib/ext" directory. You can use
fan -version
to locate your JRE directory. For MySQL the driver is packaged up as something like "mysql-connector-java-5.0.6-bin.jar". - Ensure the JDBC class is loaded into memory. The simplest way to preload the class is to ensure the classname is defined in "etc/sql/config.props" :
java.drivers=com.mysql.jdbc.Driver,oracle.jdbc.driver.OracleDriver
- Open a SqlConn instance using the JDBC URL:
db := SqlConn.open("jdbc:mysql://localhost:3306/fantest", "fantest", "fantest")
If using Microsoft SQL Server:
- Assuming you are running Java 1.6 or higher, then make sure you put only "sqljdbc4.jar" into your classpath (do not put "sqljdbc.jar" in the path)
- Classname is "com.microsoft.sqlserver.jdbc.SQLServerDriver" (for java.drivers in etc/sql/config.props)
- JDBC URL format is "jdbc:sqlserver://{host};database={name}"
Statements
SQL statements are created using the SqlConn.sql
method. After a statement has been created, it can be executed immediately by calling execute
or it can be prepared for later execution by calling prepare
.
For example, to create a table in MySQL:
db.sql("create table Books ( id integer auto_increment not null, title varchar(128) not null, author varchar(128) not null, year integer, primary key (id))").execute
Prepared statements can be parameterized by including named parameters in the SQL text. For example:
addBook := db.sql("insert into Books (title, author, year) values (@title, @author, @year)").prepare
This statement can then be executed multiple times with different parameters.
addBook.execute(["title":"David Copperfield", "author":"Charles Dickens", "year":1850]) addBook.execute(["title":"Hard Times", "author":"Charles Dickens", "year":1854]) addBook.execute(["title":"The Jungle Book", "author":"Rudyard Kipling", "year":1894]) addBook.execute(["title":"Captains Courageous", "author":"Rudyard Kipling", "year":1897])
Queries
The result of an SQL query is always a relational table described by fixed columns with zero or more rows. Fantom supports two different mechanisms for retrieving query results: as a list of Rows
or by iterating the rows with a closure.
To fetch as a list of rows:
stmt := db.sql("select title, year from Books where author = @author and year > @year").prepare dickensNovels := stmt.query(["author":"Charles Dickens", "year":1850]) kiplingNovels := stmt.query(["author":"Rudyard Kipling", "year":1890])
Or to iterate through the rows:
lastPublished := 0 stmt.queryEach(["author":"Charles Dickens", "year":0]) |Row row| { lastPublished = lastPublished.max(row->year) }
sql::Row
The rows for a given query result all share a list of sql::Cols
which describes the meta-data. You can use access the column of each row using the get(Col)
or with dynamic invoke:
// using dynamic invoke dickensNovels.each |Row book| { echo("${book->title}, ${book->year}") } // using col title := row.col("title") year := row.col("year") dickensNovels.each |Row book| { echo("${book[title]}, ${book[year]}") }
If you have a large query result, using get(Col)
provides a little better performance.
SQL/Fantom Type Mapping
The following type specifies the mapping of SQL types to Fantom types:
SQL Type Fantom Type -------- ----------- CHAR sys::Str VARCHAR sys::Str LONGVARCHAR sys::Str BIT sys::Bool TINYINT sys::Int SMALLINT sys::Int INTEGER sys::Int BIGINT sys::Int REAL sys::Float FLOAT sys::Float DOUBLE sys::Float DECIMAL sys::Decimal NUMERIC sys::Decimal BINARY sys::Buf VARBINARY sys::Buf LONGVARBINARY sys::Buf TIMESTAMP sys::DateTime DATE sys::Date TIME sys::Time catch-all sys::Str