dddbl 0.0.1.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +6 -0
- data/LICENSE +24 -0
- data/README.rdoc +188 -0
- data/Rakefile +2 -0
- data/dddbl.gemspec +21 -0
- data/example/dbdef.def +9 -0
- data/example/demo.rb +59 -0
- data/example/test.sql +12 -0
- data/lib/dddbl.rb +152 -0
- data/lib/dddbl/config.rb +119 -0
- data/lib/dddbl/pool.rb +108 -0
- data/lib/dddbl/results.rb +5 -0
- data/lib/dddbl/version.rb +3 -0
- metadata +88 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2011, André Gawron
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
- Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
- Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
- The names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
18
|
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
19
|
+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
20
|
+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
21
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
22
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
23
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
24
|
+
OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
= DDDBL - Definition Driven Database Layer
|
2
|
+
|
3
|
+
== What?
|
4
|
+
|
5
|
+
DDDBL is a library to simplify the use of (different) databases and splits up
|
6
|
+
application's and database's (e.g: SQL queries) code through an easy
|
7
|
+
and unified API. The SQL is defined in files outside of the application's source
|
8
|
+
to easiliy port the SQL to diffrent applications - or even programming languages.
|
9
|
+
Refering to the written queries using an alias makes it even more clearer and
|
10
|
+
better to understand for newcomers, who then don't have to waste time figuring out
|
11
|
+
what the query does.
|
12
|
+
|
13
|
+
== Why?
|
14
|
+
|
15
|
+
Many applications interacting with databases have the sql code directly written
|
16
|
+
in the its source. Reading, reviewing and understanding that
|
17
|
+
unfimiliar code can be hard, even more for non sql-pros. Most of the time -
|
18
|
+
to optimize the queries and the database performance in general - DBAs are hired
|
19
|
+
who are probably not familiar with the programming language used. So they have
|
20
|
+
to dig into the code and try to optimize it without breaking anything.
|
21
|
+
|
22
|
+
== Supported databases
|
23
|
+
|
24
|
+
Common RDMS like MySQL, PostgreSQL and SQLite are supported. The DDDBL uses another
|
25
|
+
gem called RDBI which simplifies the handling of several databases. For a full
|
26
|
+
list please of supported drivers please check the RDBI's github page
|
27
|
+
(https://github.com/RDBI/).
|
28
|
+
|
29
|
+
== It's a port, isn't it?
|
30
|
+
|
31
|
+
Yes. The DDDBL was orginally written in PHP and is a creation of Torsten Zühlsdorff.
|
32
|
+
For more information on the PHP version, go to http://www.dddbl.de (German only).
|
33
|
+
The ruby version will also be featured there in the near future.
|
34
|
+
|
35
|
+
=== Any differences?
|
36
|
+
|
37
|
+
Yes. As of writing, the ruby port does not include every functionality the
|
38
|
+
PHP version offers. For example:
|
39
|
+
|
40
|
+
* not as much freedom regarding manipulating query-parameter pre-execution besides
|
41
|
+
casting the binded parameters to any value extendable through TypeLib
|
42
|
+
* caching of prepared statements
|
43
|
+
|
44
|
+
== Dependecies
|
45
|
+
|
46
|
+
The database handling and querying is handled by the RDBI library. For more
|
47
|
+
information please see their README (https://github.com/RDBI/rdbi/blob/master/README.txt)
|
48
|
+
or visit the project's page on https://github.com/RDBI/ .
|
49
|
+
|
50
|
+
== Getting started
|
51
|
+
|
52
|
+
=== Database definitions
|
53
|
+
|
54
|
+
Every database the DDDBL should connect to has to be defined in a configuration
|
55
|
+
file using the ini-format. Example:
|
56
|
+
|
57
|
+
[TEST-DB]
|
58
|
+
HOST = localhost
|
59
|
+
DBNAME = test
|
60
|
+
TYPE = PostgreSQL
|
61
|
+
USER = root
|
62
|
+
PASS =
|
63
|
+
DEFAULT = true
|
64
|
+
|
65
|
+
* [TEST-DB]: alias of the database connection. It's used if the DDDBL shall
|
66
|
+
execute the query to another database
|
67
|
+
* HOST: host of the database
|
68
|
+
* DBNAME: database to select
|
69
|
+
* TYPE: driver to use
|
70
|
+
* USER: user who will be used to connect to the database
|
71
|
+
* PASS: user's password
|
72
|
+
* DEFAULT: normally, the DDDBL needs to know which database shall be used
|
73
|
+
to execute the query. If the DEFAULT flag is set to true, this connection
|
74
|
+
will be used at first - no explit connection selection needed. DEFAULT is optional.
|
75
|
+
|
76
|
+
Now the file has to be added to the database pool:
|
77
|
+
|
78
|
+
DDDBL::Pool::DB << DDDBL::Config.parse_dbs('/path/to/db/definitions.def')
|
79
|
+
|
80
|
+
The database has to be added before any queries. Adding a file with database
|
81
|
+
definitions results in establishing a connection to every defined database.
|
82
|
+
|
83
|
+
==== Selecting (another) database
|
84
|
+
|
85
|
+
If only one connection is used, the following is just "good-to-know".
|
86
|
+
To select (another) database but the default is done by calling
|
87
|
+
|
88
|
+
DDDBL::select_db('TEST-DB')
|
89
|
+
|
90
|
+
Now every query executed after #select_db will be using the database connection
|
91
|
+
defined through the alias 'TEST-DB'.
|
92
|
+
|
93
|
+
=== Query definitions
|
94
|
+
|
95
|
+
The query definition file also uses the ini-format and has to be configurated
|
96
|
+
seperately from the application (that's what the DDDBL is all about, isn't it?)
|
97
|
+
|
98
|
+
As for now, the query definitions of the PHP and ruby versions are interchangable:
|
99
|
+
|
100
|
+
[QUERY-ALIAS]
|
101
|
+
QUERY = "SELECT * FROM table"
|
102
|
+
HANDLER = MULTI
|
103
|
+
|
104
|
+
[QUERY-MULTILINE]
|
105
|
+
QUERY = "SELECT *
|
106
|
+
FROM table
|
107
|
+
WHERE foo = ?"
|
108
|
+
HANDLER = MULTI
|
109
|
+
|
110
|
+
[CREATE-TABLE]
|
111
|
+
QUERY = "CREATE TABLE foobar ( id SERIAL, name VARCHAR(50) )"
|
112
|
+
|
113
|
+
* [QUERY-ALIAS]: as with the database definition, the alias is used to
|
114
|
+
refer to the query inside of the application.
|
115
|
+
* QUERY: the sql query. It can be enclosed by " but it's optional as long
|
116
|
+
as the query fits into one line. ? is used to define parameters which will
|
117
|
+
be binded later on. No ; needed.
|
118
|
+
* HANDLER: the handler is in charge of transforming the query's result set.
|
119
|
+
There's own section devoted to that topic. HANDLER is optional (since a
|
120
|
+
result set can be empty).
|
121
|
+
|
122
|
+
As the database definitions, the query definitions also have to be added to the pool:
|
123
|
+
|
124
|
+
DDDBL::Pool << DDDBL::Config.parse_queries('/path/to/query/definitions.sql')
|
125
|
+
|
126
|
+
==== Querying
|
127
|
+
|
128
|
+
To execute a query which was be added to the pool is straight forward:
|
129
|
+
|
130
|
+
formated_result = DDDBL::get('QUERY-ALIAS')
|
131
|
+
|
132
|
+
# binds 'bar'
|
133
|
+
formated_result = DDDBL::get('QUERY-MULTILINE', 'bar')
|
134
|
+
|
135
|
+
# without a result set
|
136
|
+
DDDBL::get('CREATE-TABLE')
|
137
|
+
|
138
|
+
=== Transactions
|
139
|
+
|
140
|
+
DDDBL also supports transaction. More information on the implementation details
|
141
|
+
has to be looked up in the RDBI::Database documentation
|
142
|
+
|
143
|
+
DDDBL::transaction do
|
144
|
+
DDDBL::get('CREATE-TABLE')
|
145
|
+
|
146
|
+
# will fail because QUERY-MULTILINE expects a parameter
|
147
|
+
DDDBL::get('QUERY-MULTILINE')
|
148
|
+
end
|
149
|
+
|
150
|
+
Since the QUERY-MULTLINE will fail, the whole transaction will be rolled back
|
151
|
+
and no table will be created.
|
152
|
+
|
153
|
+
=== Creating your own result handler
|
154
|
+
|
155
|
+
As you may know or guess by now, RDBI supports transforming the query's result
|
156
|
+
set to any datastructure you want. It's as straight forward as adding a query
|
157
|
+
file to the pool, just extend the RDBI's RDBI::Result::Driver class and
|
158
|
+
implement at least #fetch. The name of the new result driver is then the name
|
159
|
+
which has to be used in the query's HANDLER configuration field.
|
160
|
+
|
161
|
+
It's also possible to pass a configuration to the result driver defined in the
|
162
|
+
query definition:
|
163
|
+
|
164
|
+
[QUERY-ALIAS]
|
165
|
+
QUERY = "SELECT bar FROM foo;"
|
166
|
+
HANDLER = MULTI INT::bar
|
167
|
+
|
168
|
+
MULTI, as in the other examples, is the name of the result driver. If there's
|
169
|
+
a whitespace following with more text, this additional text will be passed to
|
170
|
+
the result driver's constructor. The example definition will explitictly cast
|
171
|
+
every row's bar-column value to an Integer.
|
172
|
+
|
173
|
+
For more information on creating a result driver, please read the documentation
|
174
|
+
of RDBI::Result::Driver. If you want to overwrite a default result driver,
|
175
|
+
just override the class.
|
176
|
+
|
177
|
+
== Bughunting was successful!
|
178
|
+
|
179
|
+
Just report the bug through github's tracker: https://github.com/melkon/dddbl/issues
|
180
|
+
|
181
|
+
== I'd like to patch and / or help maintain DDDBL. How can I?
|
182
|
+
|
183
|
+
* Fork the project: http://github.com/melkon/dddbl
|
184
|
+
* Make your feature addition or bug fix.
|
185
|
+
|
186
|
+
== Copyright
|
187
|
+
|
188
|
+
Copyright (c) 2011 André Gawron. See LICENSE for details.
|
data/Rakefile
ADDED
data/dddbl.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "dddbl/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "dddbl"
|
6
|
+
s.version = DDDBL::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["André Gawron"]
|
9
|
+
s.email = ["andre@ziemek.de"]
|
10
|
+
s.homepage = "https://github.com/melkon/dddbl"
|
11
|
+
s.summary = %q{A Definition Driven Database Layer}
|
12
|
+
s.description = %q{A Definition Driven Database Layer. First developed in php, see: http://www.dddbl.de}
|
13
|
+
|
14
|
+
s.rubyforge_project = "dddbl"
|
15
|
+
|
16
|
+
s.add_dependency "inifile", ">= 0.4.1"
|
17
|
+
s.add_dependency "rdbi"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
data/example/dbdef.def
ADDED
data/example/demo.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rdbi'
|
2
|
+
require 'rdbi-driver-postgresql'
|
3
|
+
|
4
|
+
require 'inifile'
|
5
|
+
require 'dddbl'
|
6
|
+
|
7
|
+
class RDBI::Result::Driver::YAL < RDBI::Result::Driver
|
8
|
+
def initialize(result, *args)
|
9
|
+
super
|
10
|
+
RDBI::Util.optional_require('yaml')
|
11
|
+
end
|
12
|
+
|
13
|
+
def format_single_row(raw)
|
14
|
+
::Hash[column_names.zip(raw)].to_yaml
|
15
|
+
end
|
16
|
+
|
17
|
+
def format_multiple_rows(raw_rows)
|
18
|
+
raw_rows.collect { |row| ::Hash[column_names.zip(row)] }.to_yaml
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def column_names
|
23
|
+
@column_names ||= @result.schema.columns.map(&:name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
DDDBL::Pool::DB << DDDBL::Config.parse_dbs('dbdef.def')
|
29
|
+
DDDBL::Pool << DDDBL::Config.parse_queries('test.sql')
|
30
|
+
|
31
|
+
DDDBL::get('TEST-QUERY')
|
32
|
+
|
33
|
+
DDDBL::get('TEST-INSERT', 'andre')
|
34
|
+
DDDBL::get('TEST-INSERT', 'melkon')
|
35
|
+
|
36
|
+
before = DDDBL::get('TEST-SELECT') ; p before
|
37
|
+
|
38
|
+
begin
|
39
|
+
DDDBL::transaction do
|
40
|
+
|
41
|
+
DDDBL::get('TEST-UPDATE', 'thorny', 2)
|
42
|
+
|
43
|
+
# raises an exception
|
44
|
+
# rollback initiated
|
45
|
+
# throws an exception
|
46
|
+
DDDBL::get('TEST-INSERT')
|
47
|
+
|
48
|
+
end ; rescue => e ; end
|
49
|
+
|
50
|
+
after = DDDBL::get('TEST-SELECT') ; p after
|
51
|
+
|
52
|
+
|
53
|
+
if before.to_s == after.to_s
|
54
|
+
p "transaction rollbacked"
|
55
|
+
else
|
56
|
+
p "transaction commited"
|
57
|
+
end
|
58
|
+
|
59
|
+
DDDBL::get('TEST-DROP')
|
data/example/test.sql
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
[TEST-QUERY]
|
2
|
+
QUERY = "CREATE TABLE muff (id SERIAL, name VARCHAR(255))"
|
3
|
+
|
4
|
+
[TEST-INSERT]
|
5
|
+
QUERY = "INSERT INTO muff (name) VALUES(?)"
|
6
|
+
|
7
|
+
[TEST-SELECT]
|
8
|
+
QUERY = "SELECT * FROM muff"
|
9
|
+
HANDLER = YAML
|
10
|
+
|
11
|
+
[TEST-DROP]
|
12
|
+
QUERY = "DROP TABLE IF EXISTS muff"
|
data/lib/dddbl.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#
|
2
|
+
# DDDBL - Definition Driven Database Layer
|
3
|
+
# Copyright (c) 2011 André Gawron. See LICENSE and README for details.
|
4
|
+
#
|
5
|
+
# @example Executing Query
|
6
|
+
# result = DDDBL::get('QUERY-ALIAS', 'foo', 'bar')
|
7
|
+
#
|
8
|
+
# @example Simple Transaction
|
9
|
+
# DDDBL::transaction do
|
10
|
+
# DDDBL::get('QUERY-ALIAS', 'foo', 'bar')
|
11
|
+
# DDDBL::get('CREATE-TABLE')
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @example Select another database
|
15
|
+
# DDDBL::select_db('DATABASE-ALIAS')
|
16
|
+
#
|
17
|
+
class DDDBL
|
18
|
+
|
19
|
+
#
|
20
|
+
# a query can be associated with a specific
|
21
|
+
# database driver but if none is given, it will
|
22
|
+
# be safed in GLOBAL_POOL so every database connection
|
23
|
+
# can execute the defined query.
|
24
|
+
#
|
25
|
+
GLOBAL_POOL = :default
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
#
|
30
|
+
# selects an database connection which will then
|
31
|
+
# be used to execute queries. if the connection
|
32
|
+
# is not yet established, it will be after calling
|
33
|
+
# this method.
|
34
|
+
#
|
35
|
+
# @param [String] database_name databse alias which was defined in the config file
|
36
|
+
#
|
37
|
+
def select_db(database_name)
|
38
|
+
if @dbh == nil || @database != database_name
|
39
|
+
@dbh = RDBI::pool(database_name).get_dbh
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# executes a defined alias and returns its result set which is
|
45
|
+
# transformed by an optionally defined result hanlder before.
|
46
|
+
#
|
47
|
+
# #get uses the current database connection set by #select_db
|
48
|
+
# if none was explicity selected but a default database was set
|
49
|
+
# using the configuration files, the default connection
|
50
|
+
# will be used.
|
51
|
+
#
|
52
|
+
# @param [String] query_alias querie's alias defined in the config file
|
53
|
+
# @param [Array] *binds the parameters which shall be binded to the query
|
54
|
+
#
|
55
|
+
# @return the result set transformed by the defined handler
|
56
|
+
#
|
57
|
+
def get(query_alias, *binds)
|
58
|
+
query = DDDBL::Pool[@dbh.driver, query_alias]
|
59
|
+
res = @dbh.execute(query[:query], *binds)
|
60
|
+
res.as(query[:handler]).fetch(:all) if !query[:handler].empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# delegates method calls to RDBI::Database
|
65
|
+
# DDDBL::transaction is currently implemented this way.
|
66
|
+
#
|
67
|
+
# @param [String] method valid RDBI::Database method
|
68
|
+
# @param [Array] *args RDBI::Database method's arguments
|
69
|
+
# @param [Proc] &block and finally the optional block
|
70
|
+
#
|
71
|
+
# @return everything that the RDBI::Datbase method calls will return
|
72
|
+
# @return [Boolean] false if no database connection is set
|
73
|
+
#
|
74
|
+
# @raise [ArgumentError] if RDBI::Database does not implement given method
|
75
|
+
#
|
76
|
+
def method_missing(method, *args, &block)
|
77
|
+
return false if !@dbh.is_a? RDBI::Database
|
78
|
+
raise ArgumentError, "RDBI::Database doesnt have #{method}" if !@dbh.respond_to?(method)
|
79
|
+
|
80
|
+
@dbh.send(method, *args, &block)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# DDDBL::Utils provides methods for validation of queries
|
89
|
+
# and database definitions.
|
90
|
+
#
|
91
|
+
# @see DDDBL::Pool for implementation examples
|
92
|
+
# @see DDDBL::Pool::DB for implementation examples
|
93
|
+
#
|
94
|
+
module DDDBL::Utils
|
95
|
+
|
96
|
+
#
|
97
|
+
# sets the mandatory fiels of a configuration
|
98
|
+
#
|
99
|
+
# @param [#each] fields which are mandatory, has to implement `#each`
|
100
|
+
#
|
101
|
+
# @raise [ArgumentError] if fields does not respond to #each
|
102
|
+
#
|
103
|
+
def mandatory(fields)
|
104
|
+
raise ArgumentError, 'parameter has to implement #each' if !fields.respond_to?('each')
|
105
|
+
|
106
|
+
@mandatory = fields
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# sets the optional fiels of a configuration
|
111
|
+
#
|
112
|
+
# @param [#each] fields which are optional, has to implement #each
|
113
|
+
#
|
114
|
+
# @raise [ArgumentError] if fields does not respond to #each
|
115
|
+
#
|
116
|
+
def optional(fields)
|
117
|
+
raise ArgumentError, 'parameter has to implement #each' if !fields.respond_to?('each')
|
118
|
+
|
119
|
+
@optional = fields
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# checks check for the defined mandatory and optional values
|
124
|
+
#
|
125
|
+
# @param [#each] check data structure which implements #each
|
126
|
+
#
|
127
|
+
# @return [Boolean] true if validation was successful
|
128
|
+
# @return [Boolean] false if not
|
129
|
+
#
|
130
|
+
# @raise [ArgumentError] if check does not respond to #has_key?
|
131
|
+
# @raise [ArgumentError] if check does not respond to #empty?
|
132
|
+
#
|
133
|
+
def valid?(check)
|
134
|
+
raise ArgumentError, 'parameter has to implement has_key?' if !check.respond_to?('has_key?')
|
135
|
+
raise ArgumentError, 'parameter has to implement empty?' if !check.respond_to?('empty?')
|
136
|
+
|
137
|
+
@mandatory.each do |key|
|
138
|
+
return false if !check.has_key?(key) || check[key].empty?
|
139
|
+
end
|
140
|
+
|
141
|
+
@optional.each do |key|
|
142
|
+
return false if !check.has_key?(key)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
require 'dddbl/config'
|
149
|
+
require 'dddbl/pool'
|
150
|
+
|
151
|
+
# result handler
|
152
|
+
require 'dddbl/results'
|
data/lib/dddbl/config.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
#
|
2
|
+
# DDDBL - Definition Driven Database Layer
|
3
|
+
# Copyright (c) 2011 André Gawron. See LICENSE and README for details.
|
4
|
+
#
|
5
|
+
# DDDBL::Config parses the configuration files for queries and databases.
|
6
|
+
#
|
7
|
+
# Feel free to implement another parser (for example YAML-based files)
|
8
|
+
# and overwrite or extend DDDBL::Config. DDDBL::Config::Defaults
|
9
|
+
# will give you reasonable default values.
|
10
|
+
#
|
11
|
+
# @example Parse database definitions
|
12
|
+
# db_defintions = DDDBL::Config.parse_dbs('/path/to/file')
|
13
|
+
#
|
14
|
+
# @example Parse query definitions
|
15
|
+
# query_definitions = DDDBL::Config.parse_queries('/path/to/file')
|
16
|
+
#
|
17
|
+
module DDDBL::Config
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
#
|
22
|
+
# Offers methods to get default values for a database
|
23
|
+
# and query configuration.
|
24
|
+
#
|
25
|
+
module DDDBL::Config::Defaults
|
26
|
+
|
27
|
+
#
|
28
|
+
# returns the default values for a query configuration
|
29
|
+
#
|
30
|
+
# @param [String] query_alias the query's alias
|
31
|
+
#
|
32
|
+
# @return [Hash] the default values for a query configuration
|
33
|
+
# @option [String] :alias will contain the query_alias
|
34
|
+
# @option [String, Symbol] :type will contain DDDBL::GLOBAL_POOL
|
35
|
+
# @option [String] :handler will contain an empty string
|
36
|
+
#
|
37
|
+
def default_query(query_alias)
|
38
|
+
{
|
39
|
+
:alias => query_alias,
|
40
|
+
:type => DDDBL::GLOBAL_POOL,
|
41
|
+
:handler => ''
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# returns the default values for a database configuration
|
47
|
+
#
|
48
|
+
# @param [String] db_alias the database's alias
|
49
|
+
#
|
50
|
+
# @return [Hash] the default values for a database configuration
|
51
|
+
# @option [String] :pool_name will contain the db_alias
|
52
|
+
# @option [Boolean] :default will contain false
|
53
|
+
# @option [String] :pass will contain the user's password
|
54
|
+
#
|
55
|
+
def default_db(db_alias)
|
56
|
+
{
|
57
|
+
:pool_name => db_alias,
|
58
|
+
:default => false,
|
59
|
+
:pass => ''
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
include DDDBL::Config::Defaults
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# parses the queries from given file
|
71
|
+
#
|
72
|
+
# @param [File] file containing query definitions in IniFile-format
|
73
|
+
#
|
74
|
+
# @return [Hash<Hash>] parsed queries
|
75
|
+
#
|
76
|
+
# @raise [ArgumentError] if file does not exist
|
77
|
+
#
|
78
|
+
# @see [IniFile] for more information on the syntax
|
79
|
+
#
|
80
|
+
def self.parse_queries(file)
|
81
|
+
raise ArgumentError, "#{file} does not exist" if !File.exists?(file)
|
82
|
+
|
83
|
+
queries = {}
|
84
|
+
|
85
|
+
IniFile.load(file).each do |query_alias, query_param, query_value|
|
86
|
+
queries[query_alias] ||= default_query(query_alias)
|
87
|
+
queries[query_alias][query_param.downcase.to_sym] = query_value
|
88
|
+
end
|
89
|
+
|
90
|
+
queries
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# parses the databases from given file
|
96
|
+
#
|
97
|
+
# @param [File] file containing database definitions in IniFile-format
|
98
|
+
#
|
99
|
+
# @return [Hash<Hash>] parsed databases
|
100
|
+
#
|
101
|
+
# @raise [ArgumentError] if file does not exist
|
102
|
+
#
|
103
|
+
# @see [IniFile] for more information on the syntax
|
104
|
+
#
|
105
|
+
def self.parse_dbs(file)
|
106
|
+
raise ArgumentError, "#{file} does not exist" if !File.exists?(file)
|
107
|
+
|
108
|
+
dbs = {}
|
109
|
+
|
110
|
+
IniFile.load(file).each do |db_alias, db_param, db_value|
|
111
|
+
dbs[db_alias] ||= default_db(db_alias)
|
112
|
+
dbs[db_alias][db_param.downcase.to_sym] = db_value
|
113
|
+
end
|
114
|
+
|
115
|
+
dbs
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
data/lib/dddbl/pool.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#
|
2
|
+
# DDDBL - Definition Driven Database Layer
|
3
|
+
# Copyright (c) 2011 André Gawron. See LICENSE and README for details.
|
4
|
+
#
|
5
|
+
# DDDBL::Pool contains the parsed queries
|
6
|
+
#
|
7
|
+
class DDDBL::Pool
|
8
|
+
|
9
|
+
class << self
|
10
|
+
include DDDBL::Utils
|
11
|
+
end
|
12
|
+
|
13
|
+
mandatory [:alias, :query, :type]
|
14
|
+
optional [:handler]
|
15
|
+
|
16
|
+
#
|
17
|
+
# returns a stored query for given database driver
|
18
|
+
# if none is found, the method looks in the global "namespace"
|
19
|
+
# if there is also no query stored, it raises an exception.
|
20
|
+
#
|
21
|
+
# @param [String] dbtype database's driver name (e.g. MySQL or PostgreSQL)
|
22
|
+
# @param [String] query_alias the query's alias
|
23
|
+
#
|
24
|
+
# @return [Hash] the query configuration
|
25
|
+
#
|
26
|
+
# @raise [StandardError] if no query is stored under given alias and driver
|
27
|
+
#
|
28
|
+
# @see [DDDBL::Pool::[]=] for hash keys
|
29
|
+
# @see [DDDBL::GLOBAL_POOL] for global "namespace"
|
30
|
+
#
|
31
|
+
def self.[](dbtype, query_alias)
|
32
|
+
if @pool.has_key?(dbtype) && @pool[dbtype].has_key?(query_alias)
|
33
|
+
@pool[dbtype][query_alias]
|
34
|
+
elsif @pool.has_key?(DDDBL::GLOBAL_POOL) && @pool[DDDBL::GLOBAL_POOL].has_key?(query_alias)
|
35
|
+
@pool[DDDBL::GLOBAL_POOL][query_alias]
|
36
|
+
else
|
37
|
+
raise StandardError, "#{query_alias} not a saved query alias"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# sets a query configuration for given database pool
|
43
|
+
# optional keys have to be set but can be empty.
|
44
|
+
#
|
45
|
+
# @param [String] dbtype database's driver name (e.g. MySQL or PostgreSQL)
|
46
|
+
# @param [Hash] query_config a valid query configuration
|
47
|
+
#
|
48
|
+
# @raise [StandardError] if the query configuration is not valid
|
49
|
+
#
|
50
|
+
# @see #mandatory for mandatory query configration keys
|
51
|
+
# @see #optional for optional query configuration keys
|
52
|
+
#
|
53
|
+
def self.[]=(dbtype, query_config)
|
54
|
+
raise StandardError, 'query is not properly configured' if !valid?(query_config)
|
55
|
+
|
56
|
+
@pool ||= Hash.new(&(p=lambda{|h,k| h[k] = Hash.new(&p)}))
|
57
|
+
@pool[dbtype][query_config[:alias]] = query_config
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# shortcut for an array (or hash) of query configurations
|
62
|
+
# adds the whole query_configs to their corresponding pools.
|
63
|
+
#
|
64
|
+
# @param [#each] query_configs a list of query_configs, has to respond to #each(key, query_config)
|
65
|
+
#
|
66
|
+
def self.<<(query_configs)
|
67
|
+
query_configs.each do |key, query_config|
|
68
|
+
DDDBL::Pool[query_config[:type]] = query_config
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# DDDBL - Definition Driven Database Layer
|
76
|
+
# Copyright (c) 2011 André Gawron. See LICENSE and README for details.
|
77
|
+
#
|
78
|
+
# DDDBL::Pool::DB contains the parsed database definitions
|
79
|
+
#
|
80
|
+
class DDDBL::Pool::DB
|
81
|
+
|
82
|
+
class << self
|
83
|
+
include DDDBL::Utils
|
84
|
+
end
|
85
|
+
|
86
|
+
mandatory [:type, :pool_name, :host, :dbname, :user]
|
87
|
+
optional [:default, :pass]
|
88
|
+
|
89
|
+
#
|
90
|
+
# adds the whole db_configs to the pool,
|
91
|
+
# establishs a connection to each of them,
|
92
|
+
# and selects the default database connection
|
93
|
+
# if no connection is already set.
|
94
|
+
#
|
95
|
+
# @param [#each] query_configs a list of query_configs, has to respond to #each(key, query_config)
|
96
|
+
#
|
97
|
+
# @raise [StandardError] if the configration file is not valid
|
98
|
+
#
|
99
|
+
def self.<<(db_configs)
|
100
|
+
db_configs.each do |key, db_config|
|
101
|
+
raise StandardError, 'db_config is not valid' if !valid?(db_config)
|
102
|
+
|
103
|
+
RDBI::connect_cached(db_config[:type], db_config);
|
104
|
+
DDDBL::select_db(db_config[:pool_name]) if db_config[:default] && !DDDBL::connected?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
# the php-library makes use of MULTI as result handler
|
2
|
+
# RDBI provides with Struct the same functionality
|
3
|
+
# for portability reasons of the query config files
|
4
|
+
# a MULTI handler is introduced which is an alias for Struct
|
5
|
+
class RDBI::Result::Driver::MULTI < RDBI::Result::Driver::Struct ; end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dddbl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: 6
|
5
|
+
version: 0.0.1.alpha
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- "Andr\xC3\xA9 Gawron"
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-08-31 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: inifile
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.4.1
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rdbi
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id002
|
37
|
+
description: "A Definition Driven Database Layer. First developed in php, see: http://www.dddbl.de"
|
38
|
+
email:
|
39
|
+
- andre@ziemek.de
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- .gitignore
|
48
|
+
- LICENSE
|
49
|
+
- README.rdoc
|
50
|
+
- Rakefile
|
51
|
+
- dddbl.gemspec
|
52
|
+
- example/dbdef.def
|
53
|
+
- example/demo.rb
|
54
|
+
- example/test.sql
|
55
|
+
- lib/dddbl.rb
|
56
|
+
- lib/dddbl/config.rb
|
57
|
+
- lib/dddbl/pool.rb
|
58
|
+
- lib/dddbl/results.rb
|
59
|
+
- lib/dddbl/version.rb
|
60
|
+
homepage: https://github.com/melkon/dddbl
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 1.3.1
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project: dddbl
|
83
|
+
rubygems_version: 1.8.5
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: A Definition Driven Database Layer
|
87
|
+
test_files: []
|
88
|
+
|