native-query 0.9.0

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.
@@ -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/