Fluent Query
Fluent Query is cool way how to write SQL queries and general way how to convert series of method calls to string query in an universal and system independent manner. It may sounds like a piece of magic, but it works. It's inspired by Dibi.
General Principle
Some example:
connection.select("[id], [name]").from("[maintainers]").orderBy("[code] ASC")
Will be rendered to:
SELECT `id`, `name` FROM `maintainers` ORDER BY `code` ASC
It looks trivial, but for example call connection.heyReturnMeSomething("[yeah]")
will be transformed to:
HEY RETURN ME SOMETHING `yeah`
Which gives big potential. Of sure, escaping, aggregation and chaining
of chunks for example for WHERE
directive or another is necessary.
It's ensured by appropriate language (e.g. database) driver.
And what a more: order of tokens isn't mandatory, so with exception
of initial world (SELECT
, INSERT
etc.) you can add them according to
your needs.
Connecting
# Include it!
require "fluent-query/mysql"
require "fluent-query"
# Setup it!
driver = FluentQuery::Drivers::MySQL
settings = {
:username => "wikistatistics.net",
:password => "alfabeta",
:server => "localhost",
:port => 5432,
:database => "wikistatistics.net",
:schema => "public"
}
# Create it!
connection = FluentQuery::Connection::new(driver, settings)
Now we have connection prepared for use.
Placeholders
Simple translation calls to queries isn't the only functionality. Very
helpful are also placeholders. They works principially by the same way
as #printf
method, but are more suitable for use in queries and
supports automatic quoting. Available are:
-
%%s
which quotes string, -
%%i
which quotes integer, -
%%b
which quotes boolean, -
%%f
which quotes float, -
%%d
which quotes date, -
%%t
which quotes date-time,
And also three special:
-
%%sql
which quotes subquery (expects query object), -
%%and
which joins input byAND
operator (expects hash), -
%%or
which joins input byOR
operator (expects hash).
An example:
connection.select("[id], [name]") \
.from("[maintainers]") \
.where("[id] = %%i AND company = %%s", 5, "Wikia") \
.where("[language] IN %%l", ["cz", "en"]) \
.or \
.where("[active] IS %%b", true)
Will be transformed to:
SELECT `id`, `name` FROM `maintainers`
WHERE `id` = 5
AND `company` = "Wikia"
AND `language` IN ("cz", "en")
OR `active` IS TRUE
It's way how to write complex or special queries. But direct values assigning is supported, so for example:
connection.select(:id, :name) \
.from(:maintainers) \
.where(:id => 5, :company => "Wikia") \
.where("[language] IN %%l", ["cz", "en"]) # %l will join items by commas
.or \
.where(:active => true)
Will give you expected result too and as you can see, it's much more readable, flexible, thus it's preferred.
Checking Out the Results
Query results can be executed by #execute
which returns result object
or by #do
which returns count of affected rows. Following methods for
checking out the results are available:
-
#each
which iterates through all returned rows, -
#one
which returns first row only, -
#single
which returns first value fo first row, -
#assoc
which allows building complex Hashes (see below).
Associative Fetching
Special associative method is the assoc
one which is directly inspired
by appropriate feature of the Dibi layer. It's aim is automatic
aggregation of returned rows to multidimensional Hashes.
Simply give it key names from your dataset. Be warn, only one or two levels (e.g. dimesions in resultant Hash) are supported:
records = connection.select(:maintainer_id, :language) \
.from(:sites) \
.execute.assoc(:maintainer_id, :language)
Will transform the dataset:
# maintainer_id, language, name
[1, "en", "English Wikipedia"],
[1, "es", "Spain Wikipedia"],
[2, "cs", "Czech Wikihow"],
[2, "ja", "Japan Wikihow"],
To the following structure:
1 => {
"en" => "English Wikipedia",
"es" => "Spain Wikipedia"
},
2 => {
"cs" => "Czech Wikihow",
"ja" => "Japan Wikihow"
}
Inserts, Updates and Deletes
Inserting, updating and deleteing the records works by the same way as selecting. Some examples:
connection.insert(:maintainers, :name => "Wikimedia", :country => "United States")
# Will be:
# INSERT INTO `maintainers` (`name`, `country`) VALUES ("Wikimedia", "United States")
connection.update(:maintainers).set(:country => "Czech Republic").where(:id => 10).limit(1)
# Will be:
# UPDATE `maintainers` SET `country` = "Czech Republic" WHERE `id` = 10 LIMIT 1
connection.delete(:maintainers).where(:id => 10).limit(1)
# Will be:
# DELETE FROM `maintainers` WHERE `id` = 10 LIMIT 1
Transactions
Transactions support is available manual:
-
connection.begin
, -
connection.commit
, -
connection.rollback
.
Or by automatic way:
connection.transaction do
#...
end
Compiled and Prepared Queries
Queries can be pre-prepared and pre-optimized by two different methods:
-
#compile
which compiles query to form of array of direct callbacks, so builds it and quotes all identifiers, keeps intact placeholders only, -
#prepare
which transforms compiled query to prepared form as it's known from DBD or PDO if it's supported by driver.
Simply call one of these methods upon the query and use resultant query as usuall (of sure, without methods which would change it because it's compiled so cannot be further changed).
Also note, SQL token calls cannot be called by mandatory way (e.g. you
can call #order
before #where
etc.), but they will be reordered
in resultant both compiled and prepared query, so arguments given to
execute must be taken to call in correct order according to resultant
SQL query. So in case of using compiled or prepared statements, it's
good idea to write calls in the same order as SQL requires.
Examples
More examples or tutorials than these above aren't available. It's in development, although stable and production ready state. For well documented and fully usable application see Native Query.
Contributing
- Fork it.
- Create a branch (
git checkout -b 20101220-my-change
). - Commit your changes (
git commit -am "Added something"
). - Push to the branch (
git push origin 20101220-my-change
). - Create an Issue with a link to your branch.
- Enjoy a refreshing Diet Coke and wait.
Copyright
Copyright © 2009-2012 Martin Kozák. See LICENSE.txt
for
further details.