native-query 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ gem "fluent-query", ">= 0.9.0"
5
+ gem "hash-utils", ">= 0.18.0"
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "bundler", "~> 1.0.13"
11
+ gem "jeweler", "~> 1.6.0"
12
+ end
13
+
14
+
15
+
16
+ # fluent-query-sql
17
+ # fluent-query-dbh
18
+ # fluent-query-mysql
19
+ # fluent-query-sqlite
20
+ # fluent-query-postgresql
21
+ # native-query
@@ -0,0 +1,25 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ fluent-query (0.9.0)
6
+ abstract (>= 1.0.0)
7
+ hash-utils (>= 0.18.0)
8
+ hashie (>= 1.0.0)
9
+ git (1.2.5)
10
+ hash-utils (0.18.0)
11
+ hashie (1.0.0)
12
+ jeweler (1.6.3)
13
+ bundler (~> 1.0)
14
+ git (>= 1.2.5)
15
+ rake
16
+ rake (0.9.2)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ bundler (~> 1.0.13)
23
+ fluent-query (>= 0.9.0)
24
+ hash-utils (>= 0.18.0)
25
+ jeweler (~> 1.6.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 - 2011 Martin Kozák (martinkozak@martinkozak.net)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,462 @@
1
+ Native Query
2
+ ============
3
+
4
+ **Native Query** is cool way how to speak with database server. It's
5
+ ellegant and very ruby SQL query helper which works by similar way as
6
+ Arel or another ORM selecting logic. It's derived from [Dibi][1]
7
+ database layer in its ideas, so is much more simple and (of sure) much
8
+ more KISS, readable and straightforward.
9
+
10
+ It's build on top of the general [Fluent Query][2] library which servers as
11
+ underlying layer, so can be extended to almost whatever – and not-only
12
+ database – platform.
13
+
14
+ ### Connecting
15
+
16
+ # Include it!
17
+ require "fluent-query/mysql"
18
+ require "native-query"
19
+
20
+ # Setup it!
21
+ driver = FluentQuery::Drivers::MySQL
22
+ settings = {
23
+ :username => "wikistatistics.net",
24
+ :password => "alfabeta",
25
+ :server => "localhost",
26
+ :port => 5432,
27
+ :database => "wikistatistics.net",
28
+ :schema => "public"
29
+ }
30
+
31
+ # Create it!
32
+ model = NativeQuery::Model::new(driver, settings)
33
+
34
+ Now we have model prepared for use.
35
+
36
+ ### Selecting
37
+
38
+ Simply call method accroding to table name above the model. Its
39
+ arguments will be fields which you would like to select:
40
+
41
+ records = model.maintainers :name, :code do
42
+ ...
43
+ get.all
44
+ end
45
+
46
+ The last command in the block is getter. You can take `all` records,
47
+ `one` record only or `single` (first) value of first row. `assoc` method
48
+ is described below.
49
+
50
+ Traversing through returned records is simple of sure:
51
+
52
+ records.each do |row|
53
+ p row.code, row.name
54
+ end
55
+
56
+ #### Associative Fetching
57
+
58
+ Special associative method is the `assoc` one which is directly inspired
59
+ by appropriate feature of the [Dibi][1] layer. It's aim is automatic
60
+ aggregation of returned rows to multidimensional Hashes.
61
+
62
+ Simply give it key names from your dataset. Be warn, only one or two
63
+ levels (e.g. dimesions in resultant Hash) are supported:
64
+
65
+ records = model.sites :maintainer_id, :language, :name do
66
+ # ...
67
+ get.assoc :maintainer_id, :language
68
+ end
69
+
70
+ Will transform the dataset:
71
+
72
+ # maintainer_id, language, name
73
+ [1, "en", "English Wikipedia"],
74
+ [1, "es", "Spain Wikipedia"],
75
+ [2, "cs", "Czech Wikihow"],
76
+ [2, "ja", "Japan Wikihow"],
77
+
78
+ To the following structure:
79
+
80
+ 1 => {
81
+ "en" => "English Wikipedia",
82
+ "es" => "Spain Wikipedia"
83
+ },
84
+
85
+ 2 => {
86
+ "cs" => "Czech Wikihow",
87
+ "ja" => "Japan Wikihow"
88
+ }
89
+
90
+ ### Conditions, ordering and limits
91
+
92
+ Limits and offsets are simple too:
93
+
94
+ records = model.maintainers :name, :code do
95
+ # ...
96
+ offset 5
97
+ limit 3
98
+ # ...
99
+ end
100
+
101
+ Will select sixth, seventh and eighth record.
102
+
103
+ #### Conditions
104
+
105
+ Conditions (`WHERE` equivalent) receives Ruby's native data types. So
106
+ simply call:
107
+
108
+ records = model.maintainers :name, :code do
109
+ where :active => true
110
+ where :id => 5
111
+ # ...
112
+ end
113
+
114
+ These confitions are simple and `AND` equivalency of sure. Because aim
115
+ is to be simple and to don't complicate rather nice interface by giant
116
+ stuff of sophisticated and complicated calls, you can provide whatever
117
+ condition using FluentQuery strings:
118
+
119
+ records = model.maintainers :name, :code do
120
+ # ...
121
+ where "[id] > 5"
122
+ where "[name] IN %%l", names
123
+ where "%%or", :id => 10, :name => "Wikia, Inc."
124
+ # ...
125
+ end
126
+
127
+ Brackets always means "this identifer is a field name". See description
128
+ of the [Fluent Query][2] below.
129
+
130
+ #### Ordering
131
+
132
+ Orders work by very predictable way. For example:
133
+
134
+ records = model.maintainers :name, :code do
135
+ # ...
136
+ order :name, :desc
137
+ order :date, :asc, :id, :asc
138
+ # ...
139
+ end
140
+
141
+ Means "order by `name DESC` and then by `date, id ASC`". You can combine
142
+ both of styles mentioned above. If you need order by joined fields,
143
+ simply replace symbol by array with table name and field name as you can
144
+ see in advanced example below.
145
+
146
+ ### Joining
147
+
148
+ Two kinds of joining are available: *automatic* and *manual*. They have
149
+ the same syntax principially, for manual joining is necessary to provide
150
+ more informations of sure.
151
+
152
+ #### Manual Joining
153
+
154
+ For manual joining simply type:
155
+
156
+ records = model.maintainers :name, :code, :sites_code, :sites_name do
157
+ # ...
158
+ sites :code, :name, :language_name do
159
+ direct :site_id => :id
160
+ # ...
161
+ end
162
+ # ...
163
+ end
164
+
165
+ Which means select from table `maintainers` and join it with
166
+ table `sites` by N:1 (direct) relation. Yes, you can join directly by
167
+ "calling the table" and treating its block as your primary table. It's
168
+ ellegant and very readable. For next level of joining simply do the same
169
+ in the inner block.
170
+
171
+ All fields selected from the joined table are prefixed by its name and
172
+ it's necessary of sure to tell interpret you want return them, as you
173
+ can see above. It's practical because you know about orgination of the
174
+ field whenever further in your source code.
175
+
176
+ Slightly more complicated is M:N relation type which works in
177
+ semiautomatic way only:
178
+
179
+ records = model.maintainers :name, :code, :sites_code, :sites_name do
180
+ # ...
181
+ sites :code, :name, :language_name do
182
+ indirect :sites_maintainers, :id => :id
183
+ # ...
184
+ end
185
+ # ...
186
+ end
187
+
188
+ Which means the same as:
189
+
190
+ SELECT ... FROM `maintainers`
191
+ JOIN `sites_maintainers` ON `maintainers`.`id` = `sites_maintainers`.`maintainers_id`
192
+ JOIN `sites` ON `sites_maintainers`.`sites_id` = `site`.`id`
193
+ ...
194
+
195
+ Only `LEFT JOIN` is supported. For other joining types, use direct
196
+ *Fluent Query* interface (see below). Special conditions in `ON` clausule
197
+ is possible to achieve simply by giving the *Fluent Query* string:
198
+
199
+ records = model.maintainers :name, :code, :sites_code, :sites_name do
200
+ # ...
201
+ sites :code, :name, :language_name do
202
+ indirect :sites_maintainers, "[maintainers.id] = [sites_maintainers.strange_1]", "[sites_maintainers.strange_2] = [site.id]"
203
+ # ...
204
+ end
205
+ # ...
206
+ end
207
+
208
+ And the same for direct joining of sure.
209
+
210
+ #### Automatic joining
211
+
212
+ Automatic joining is recommended joining way although it has some strict
213
+ requirements for table and field names:
214
+
215
+ * primary keys are expected to be named `id`,
216
+ * foreign key fields are expected to be named `<target-table>_id`,
217
+ * M:N linking tables are expected to be named `<source-table>_<target-table>`.
218
+
219
+ But then you can use the following nice syntax for both *direct*:
220
+
221
+ records = model.maintainers :name, :code, :sites_code, :sites_name do
222
+ # ...
223
+ sites :code, :name, :language_name do
224
+ direct
225
+ # ...
226
+ end
227
+ # ...
228
+ end
229
+
230
+ Which will be transformed approximately (it's driver dependent) into:
231
+
232
+ SELECT `name`, `code`, `sites`.`code`, `sites`.`name`
233
+ FROM `maintainers`
234
+ JOIN `sites` ON `maintainers`.`id` = `sites`.`maintainer_id`
235
+ ...
236
+
237
+ Or *indirect*:
238
+
239
+ records = model.maintainers :name, :code, :sites_code, :sites_name do
240
+ # ...
241
+ sites :code, :name, :language_name do
242
+ indirect
243
+ # ...
244
+ end
245
+ # ...
246
+ end
247
+
248
+ Which will be transformed approximately (it's driver dependent) into:
249
+
250
+ SELECT `name`, `code`, `sites`.`code` AS `sites_code`, `sites`.`name` AS `sites_name`
251
+ FROM `maintainers`
252
+ JOIN `maintainers_sites` ON `maintainers`.`id` = `maintainers_sites`.`maintainer_id`
253
+ JOIN `sites` ON `maintainers_sites`.`site_id` = `site`.`id`
254
+ ...
255
+
256
+ Should be noted, if you need *backward indirect* joining (so in opposite
257
+ direction than in examples above), simply call `direct backward` or
258
+ `indirect backward`.
259
+
260
+ ### Inserts, Updates and Deletes
261
+
262
+ Native Query doesn't support native inserting, updating and deleting,
263
+ but provides bridge to appropriate Fluent Query methods. Some examples:
264
+
265
+ model.insert(:maintainers, :name => "Wikimedia", :country => "United States")
266
+
267
+ # Will be:
268
+ # INSERT INTO `maintainers` (`name`, `country`) VALUES ("Wikimedia", "United States")
269
+
270
+ model.update(:maintainers).set(:country => "Czech Republic").where(:id => 10).limit(1)
271
+
272
+ # Will be:
273
+ # UPDATE `maintainers` SET `country` = "Czech Republic" WHERE `id` = 10 LIMIT 1
274
+
275
+ model.delete(:maintainers).where(:id => 10).limit(1)
276
+
277
+ # Will be:
278
+ # DELETE FROM `maintainers` WHERE `id` = 10 LIMIT 1
279
+
280
+ #### Transactions
281
+
282
+ Transactions support is available manual:
283
+
284
+ * `model.begin`
285
+ * `model.commit`
286
+ * `model.rollback`
287
+
288
+ Or by automatic way:
289
+
290
+ model.transaction do
291
+ #...
292
+ end
293
+
294
+ ### Fluent Queries
295
+
296
+ The *Native Query* library is built on top of the [Fluent Query][2]
297
+ library which provides way how to fluently translate series of method
298
+ calls to some query language (but typically SQL). Some example:
299
+
300
+ model.select("[id], [name]").from("[maintainers]").orderBy("[code] ASC")
301
+
302
+ Will be rendered to:
303
+
304
+ SELECT `id`, `name` FROM `maintainers` ORDER BY `code` ASC
305
+
306
+ It looks trivial, but for example call `model.heyReturnMeSomething("[yeah]")`
307
+ will be transformed to:
308
+
309
+ HEY RETURN ME SOMETHING `yeah`
310
+
311
+ Which gives big potential. Of sure, escaping, aggregation and chaining
312
+ of chunks for example for `WHERE` directive or another is necessary.
313
+ It's ensured by appropriate *language* (e.g. database) *driver*.
314
+
315
+ And what a more: order of tokens isn't mandatory, so with exception
316
+ of initial world (`SELECT`, `INSERT` etc.) you can add them according to
317
+ your needs.
318
+
319
+ #### Placeholders
320
+
321
+ Simple translation calls to queries isn't the only functionality. Very
322
+ helpful are also *placeholders*. They works principially by the same way
323
+ as `#printf` method, but are more suitable for use in queries and
324
+ supports automatic quoting. Available are:
325
+
326
+ * `%%s` which quotes string,
327
+ * `%%i` which quotes integer,
328
+ * `%%b` which quotes boolean,
329
+ * `%%f` which quotes float,
330
+ * `%%d` which quotes date,
331
+ * `%%t` which quotes date-time,
332
+
333
+ And also three special:
334
+
335
+ * `%%sql` which quotes subquery (expects query object),
336
+ * `%%and` which joins input by `AND` operator (expects hash),
337
+ * `%%or` which joins input by `OR` operator (expects hash).
338
+
339
+ An example:
340
+
341
+ model.select("[id], [name]") \
342
+ .from("[maintainers]") \
343
+ .where("[id] = %%i AND company = %%s", 5, "Wikia") \
344
+ .where("[language] IN %%l", ["cz", "en"]) \
345
+ .or \
346
+ .where("[active] IS %%b", true)
347
+
348
+ Will be transformed to:
349
+
350
+ SELECT `id`, `name` FROM `maintainers`
351
+ WHERE `id` = 5
352
+ AND `company` = "Wikia"
353
+ AND `language` IN ("cz", "en")
354
+ OR `active` IS TRUE
355
+
356
+ It's way how to write complex or special queries. But **direct values
357
+ assigning is supported**, so for example:
358
+
359
+ model.select(:id, :name) \
360
+ .from(:maintainers) \
361
+ .where(:id => 5, :company => "Wikia") \
362
+ .where("[language] IN %%l", ["cz", "en"]) # %l will join items by commas
363
+ .or \
364
+ .where(:active => true)
365
+
366
+ Will give you expected result too and as you can see, it's much more
367
+ readable, flexible, so preferred.
368
+
369
+ #### Relation to Native Query
370
+
371
+ You can take Fluent Query object from the Native Query by:
372
+
373
+ # Query it!
374
+ query = model.maintainers :name, :code do
375
+ where :active => true
376
+ order :name, :asc
377
+ limit 1
378
+ get.query # takes the Fluent Query object
379
+ end
380
+
381
+ query.execute!
382
+
383
+ And if necessary build it by `#build` method to string. Build method is
384
+ also available above Native Query object directly. To execute query or
385
+ fetch data is possible through `#do(*args)` or `#execute(*args)`. Result
386
+ will be result object similar to Native Query's one.
387
+
388
+ ### Examples
389
+
390
+ Simple example:
391
+
392
+ # Query it!
393
+ records = model.maintainers :name, :code do
394
+ where :active => true
395
+ order :name, :asc
396
+ limit 1
397
+ get.all
398
+ end
399
+
400
+ Will be transformed to:
401
+
402
+ SELECT `name`, `code` FROM `maintainers`
403
+ WHERE `active` IS TRUE
404
+ ORDER BY `name` ASC
405
+ LIMIT 1
406
+
407
+ Advanced automatic joining (advanced example):
408
+
409
+ # here selects two fields from 'projects' table and two other fields from joined 'sites' table
410
+ projects = model.projects :name, :code, :sites_code, :sites_name do
411
+ sites :code, :name, :language_name do
412
+ where :active => true
413
+ end
414
+
415
+ maintainers do # joins 'projects' table with table 'maintainers'
416
+ indirect backward # ...by indirect way, so M:N
417
+ where :active => true
418
+ where :id => 10
419
+ end
420
+
421
+ where :active => true
422
+ order :code, [:sites, :code]
423
+
424
+ get.assoc(:code, :sites_code)
425
+ end
426
+
427
+ Will be transformed to:
428
+
429
+ SELECT `name`, `code`, `sites`.`code` AS `sites_code`, `sites`.`name` AS `sites_name`
430
+ FROM `projects`
431
+ JOIN `sites` ON `projects`.`id` = `sites`.`project_id`
432
+ JOIN `maintainers_projects`
433
+ ON `projects`.`id` = `maintainers_projects`.`project_id`
434
+ JOIN `maintainers`
435
+ ON `maintainers`.`id` = `maintainers_projects`.`maintainer_id`
436
+ WHERE `sites`.`active` IS TRUE
437
+ AND `maintainers`.`active` IS TRUE
438
+ AND `maintainers`.`id` = 10
439
+ AND `active` IS TRUE
440
+ ORDER BY `code`, `sites`.`code` ASC
441
+
442
+
443
+ Contributing
444
+ ------------
445
+
446
+ 1. Fork it.
447
+ 2. Create a branch (`git checkout -b 20101220-my-change`).
448
+ 3. Commit your changes (`git commit -am "Added something"`).
449
+ 4. Push to the branch (`git push origin 20101220-my-change`).
450
+ 5. Create an [Issue][3] with a link to your branch.
451
+ 6. Enjoy a refreshing Diet Coke and wait.
452
+
453
+ Copyright
454
+ ---------
455
+
456
+ Copyright &copy; 2010-2011 [Martin Kozák][4]. See `LICENSE.txt` for
457
+ further details.
458
+
459
+ [1]: http://dibiphp.com/
460
+ [2]: https://github.com/martinkozak/fluent-query
461
+ [3]: http://github.com/martinkozak/native-query/issues
462
+ [4]: http://www.martinkozak.net/