appquery 0.4.0.rc1 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 585ecafd973b1dd9fc8c944b93c64998a8b983438250cbd1ec81317d9e2362d4
4
- data.tar.gz: 8197dc68853e7299266a0f8c2dd7fa102bbbf7e743680786bce4c7c2eedcd719
3
+ metadata.gz: 0ca8349f2df1ddf7e2248314238dd72ff5623d2924f1265f385cb1dfde8b274d
4
+ data.tar.gz: 12440ed32a10ca28acd01df89175906e2ed2f77105905ed44f905bc54def4c5d
5
5
  SHA512:
6
- metadata.gz: 37b5baf0f42dbad9ee1f356e0541b5677cb93a934d38ad851931142f79a041337f77cbbd4f56c1e880b4db9140460237afa8627eb0f6f9c73461fc1f6b3843cb
7
- data.tar.gz: 3edc3e28e09a648a3d14634beab10cdde1be8d1b31c6c8163fd2f19844fab31a5ecf762ddcef9311f18d1356fe6e2c765f99dd2d08706284c687e94d21c36261
6
+ metadata.gz: 8154cfbf83c7327cc6bdfbe501430007fb4ed9a5ecfb65f6d751358e3721ca853b9d4655968955147fa2e3aa4ea898db9f71bd3da19647e4e550d2887121daab
7
+ data.tar.gz: a51fc511fa7b5da2bf03c4d63910abb1bb980b95fc300bf188a8749e726e965d781d14d28ffa3240dbca515c7d2bb0f5e83041eae61bb6ea7e815a1f7a13d19f
data/.irbrc CHANGED
@@ -17,4 +17,13 @@ IRB.conf[:PROMPT][:APPQUERY] = {
17
17
  PROMPT_C: "#{db_indicator}appquery* ",
18
18
  RETURN: "=> %s\n"
19
19
  }
20
+
21
+ # copy from history easier as prompt is not repeated across lines
22
+ IRB.conf[:PROMPT][:CLEAN] = {
23
+ PROMPT_I: ">> ",
24
+ PROMPT_S: " ", # same width as ">> "
25
+ PROMPT_C: " ",
26
+ PROMPT_N: " ",
27
+ RETURN: "=> %s\n"
28
+ }
20
29
  IRB.conf[:PROMPT_MODE] = :APPQUERY
data/.yardopts ADDED
@@ -0,0 +1,11 @@
1
+ --readme README.md
2
+ --markup markdown
3
+ --no-private
4
+ --output-dir doc
5
+ --exclude tmp/
6
+ --exclude spec/
7
+ --exclude examples/
8
+ --exclude gemfiles/
9
+ lib/**/*.rb
10
+ - LICENSE.txt
11
+ - CHANGELOG.md
data/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2024-10-13
3
+ ## [0.5.0] - 2025-12-21
4
4
 
5
- - Initial release
5
+ ### 💥 Breaking Changes
6
+
7
+ - 🔄 **`select:` keyword argument removed** — use positional argument instead
8
+ ```ruby
9
+ # before
10
+ query.select_all(select: "SELECT * FROM :_")
11
+ # after
12
+ query.select_all("SELECT * FROM :_")
13
+ ```
14
+
15
+ ### ✨ Features
16
+
17
+ - 🍾 **Add paginate ERB-helper**
18
+ ```ruby
19
+ SELECT * FROM articles
20
+ <%= paginate(page: 1, per_page: 15) %>
21
+ # SELECT * FROM articles LIMIT 15 OFFSET 0
22
+ ```
23
+ - 🧰 **Resolve query without extension**
24
+ `AppQuery[:weekly_sales]` loads `weekly_sales.sql` or `weekly_sales.sql.erb`.
25
+ - 🔗 **Nested result queries** via `with_select` — chain transformations using `:_` placeholder to reference the previous result
26
+ ```ruby
27
+ active_users = AppQuery("SELECT * FROM users").with_select("SELECT * FROM :_ WHERE active")
28
+ active_users.count("SELECT * FROM :_ WHERE admin")
29
+ ```
30
+ - 🚀 **New methods**: `#column`, `#ids`, `#count`, `#entries` — efficient shortcuts that only fetch what you need
31
+ ```ruby
32
+ query.column(:email) # SELECT email only
33
+ query.ids # SELECT id only
34
+ query.count # SELECT COUNT(*) only
35
+ query.entries # shorthand for select_all.entries
36
+ ```
37
+
38
+ ### 🐛 Fixes
39
+
40
+ - 🔧 Fix leading whitespace in `prepend_cte` causing parse errors
41
+ - 🔧 Fix binds being reset when no placeholders found
42
+ - ⚡ `select_one` now uses `LIMIT 1` for better performance
43
+
44
+ ### 📚 Documentation
45
+
46
+ - 📖 Revised README with cleaner intro and examples
47
+ - 🏠 Added example Rails app in `examples/demo`
48
+
49
+ ## [0.4.0] - 2025-12-15
50
+
51
+ ### features
52
+
53
+ - add insert, update and delete
54
+ - API docs at [eval.github.io/appquery](https://eval.github.io/appquery)
55
+ - add ERB-helpers [values, bind and quote ](https://eval.github.io/appquery/AppQuery/RenderHelpers.html).
56
+ - enabled trusted publishing to rubygems.org
data/README.md CHANGED
@@ -1,68 +1,42 @@
1
1
  # AppQuery - raw SQL 🥦, cooked :stew:
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/appquery.svg)](https://badge.fury.io/rb/appquery)
4
+ [![API Docs](https://img.shields.io/badge/API_Docs-YARD-blue.svg)](https://eval.github.io/appquery/)
4
5
 
5
- A Rubygem :gem: that makes working with raw SQL (READ) queries in Rails projects more convenient.
6
- Specifically it provides:
7
- - **...a dedicated folder for queries**
8
- e.g. `app/queries/reports/weekly.sql` is instantiated via `AppQuery["reports/weekly"]`.
9
- - **...Rails/rspec generators**
10
- ```
11
- $ rails generate query reports/weekly
12
- create app/queries/reports/weekly.sql
13
- invoke rspec
14
- create spec/queries/reports/weekly_query_spec.rb
15
- ```
16
- - **...ERB templating**
17
- Simple ERB templating with helper-functions:
18
- ```sql
19
- -- app/queries/contracts.sql.erb
20
- SELECT * FROM contracts
21
- <%= order_by(order) %>
22
- ```
23
- ```ruby
24
- AppQuery["contracts.sql.erb"].render(order: {year: :desc, month: :desc}).select_all
25
- ```
26
- - **...positional and named binds**
27
- Intuitive binds:
28
- ```ruby
29
- AppQuery(%{select now() - (:interval)::interval as some_date}).select_value(binds: {interval: '1 day'})
30
- AppQuery(<<~SQL).select_all(binds: [2.day.ago, Time.now, '5 minutes']).column("series")
31
- select generate_series($1::timestamp, $2::timestamp, $3::interval) as series
32
- SQL
33
- ```
34
- - **...casting**
35
- Automatic and custom casting:
36
- ```ruby
37
- AppQuery(%{select array[1,2]}).select_value #=> [1,2]
38
- cast = {"data" => ActiveRecord::Type::Json.new}
39
- AppQuery(%{select '{"a": 1}' as data}).select_value(cast:)
40
- ```
41
- - **...helpers to rewrite a query for introspection during development and testing**
42
- See what a CTE yields: `query.select_all(select: "SELECT * FROM some_cte")`.
43
- Query the end result: `query.select_one(select: "SELECT COUNT(*) FROM _ WHERE ...")`.
44
- Append/prepend CTEs:
45
- ```ruby
46
- query.prepend_cte(<<~CTE)
47
- articles(id, title) AS (
48
- VALUES(1, 'Some title'),
49
- (2, 'Another article'))
50
- CTE
51
- ```
52
- - **...rspec-helpers**
53
- ```ruby
54
- RSpec.describe "AppQuery reports/weekly", type: :query do
55
- describe "CTE some_cte" do
56
- # see what this CTE yields
57
- expect(described_query.select_all(select: "select * from some_cte")).to \
58
- include(a_hash_including("id" => 1))
59
-
60
- # shorter: the query and CTE are derived from the describe-descriptions so this suffices:
61
- expect(select_all).to include ...
62
- ```
6
+ A Ruby gem for working with raw SQL in Rails. Store queries in `app/queries/`, execute them with proper type casting, and filter/transform results using CTEs.
7
+
8
+ ```ruby
9
+ # Load and execute
10
+ week = AppQuery[:weekly_sales].with_binds(week: 1, year: 2025)
11
+ week.entries
12
+ #=> [{"week" => 2025-01-13, "category" => "Electronics", "revenue" => 12500, "target_met" => true}, ...]
13
+
14
+ # Filter results (query wraps in CTE, :_ references it)
15
+ week.count("SELECT * FROM :_ WHERE NOT target_met")
16
+ #=> 3
17
+
18
+ # Extract a column efficiently (only fetches that column)
19
+ week.column(:category)
20
+ #=> ["Electronics", "Clothing", "Home & Garden"]
21
+
22
+ # Named binds with defaults
23
+ AppQuery[:weekly_sales].select_all(binds: {min_revenue: 5000})
24
+
25
+ # ERB templating
26
+ AppQuery("SELECT * FROM contracts <%= order_by(ordering) %>")
27
+ .render(ordering: {year: :desc}).select_all
28
+
29
+ # Custom type casting
30
+ AppQuery("SELECT metadata FROM products").select_all(cast: {"metadata" => ActiveRecord::Type::Json.new})
31
+
32
+ # Inspect/mock CTEs for testing
33
+ query.prepend_cte("sales AS (SELECT * FROM mock_data)")
34
+ ```
35
+
36
+ **Highlights**: query files with generator · `select_all`/`select_one`/`select_value`/`count`/`column`/`ids` · query transformation via CTEs · immutable (derive new queries from existing) · named binds · ERB helpers (`order_by`, `paginate`, `values`, `bind`) · automatic + custom type casting · RSpec integration
63
37
 
64
38
  > [!IMPORTANT]
65
- > **Status**: alpha. API might change. See the CHANGELOG for breaking changes when upgrading.
39
+ > **Status**: alpha. API might change. See [the CHANGELOG](./CHANGELOG.md) for breaking changes when upgrading.
66
40
  >
67
41
 
68
42
  ## Rationale
@@ -107,14 +81,23 @@ The prompt indicates what adapter the example uses:
107
81
  => "2025-05-10"
108
82
 
109
83
  # binds
110
- # positional binds
111
- [postgresql]> AppQuery(%{select now() - ($1)::interval as date}).select_value(binds: ['2 days'])
112
- # named binds
84
+ ## named binds
113
85
  [postgresql]> AppQuery(%{select now() - (:interval)::interval as date}).select_value(binds: {interval: '2 days'})
114
86
 
87
+ ## not all binds need to be provided (ie they are nil by default) - so defaults can be added in SQL:
88
+ [postgresql]> AppQuery(<<~SQL).select_all(binds: {ts1: 2.day.ago, ts2: Time.now}).column("series")
89
+ SELECT
90
+ generate_series(:ts1::timestamp, COALESCE(:ts2, :ts2::timestamp, COALESCE(:interval, '5 minutes')::interval)
91
+ AS series
92
+ SQL
93
+
115
94
  # casting
116
- [postgresql]> AppQuery(%{select date('now') as today}).select_all(cast: true).to_a
117
- => [{"today" => Sat, 10 May 2025}]
95
+ ## Cast values are used by default:
96
+ [postgresql]> AppQuery(%{select date('now')}).select_first
97
+ => {"today" => Sat, 10 May 2025}
98
+ ## compare ActiveRecord
99
+ [postgresql]> ActiveRecord::Base.connection.select_first(%{select date('now') as today})
100
+ => {"today" => "2025-12-20"}
118
101
 
119
102
  ## SQLite doesn't have a notion of dates or timestamp's so casting won't do anything:
120
103
  [sqlite]> AppQuery(%{select date('now') as today}).select_one(cast: true)
@@ -137,10 +120,12 @@ casts = {"today" => ActiveRecord::Type::Date.new}
137
120
  SQL
138
121
 
139
122
  ## query the articles-CTE
140
- [postgresql]> q.select_all(select: %{select * from articles where id < 2}).to_a
123
+ [postgresql]> q.select_all(%{select * from articles where id < 2}).to_a
141
124
 
142
- ## query the end-result (available as the CTE named '_')
143
- [postgresql]> q.select_one(select: %{select * from _ limit 1})
125
+ ## query the end-result (available via the placeholder ':_')
126
+ [postgresql]> q.select_one(%{select * from :_ limit 1})
127
+ ### shorthand for that
128
+ [postgresql]> q.first
144
129
 
145
130
  ## ERB templating
146
131
  # Extract a query from q that can be sorted dynamically:
@@ -167,7 +152,7 @@ SQL
167
152
  ### ...in a Rails project
168
153
 
169
154
  > [!NOTE]
170
- > The included [example Rails app](./examples/ror) contains all data and queries described below.
155
+ > The included [example Rails app](./examples/demo) contains all data and queries described below.
171
156
 
172
157
  Create a query:
173
158
  ```bash
@@ -177,15 +162,15 @@ rails g query recent_articles
177
162
  Have some SQL (for SQLite, in this example):
178
163
  ```sql
179
164
  -- app/queries/recent_articles.sql
180
- WITH settings(default_min_published_on) as (
181
- values(datetime('now', '-6 months'))
165
+ WITH settings(min_published_on) as (
166
+ values(COALESCE(:since, datetime('now', '-6 months')))
182
167
  ),
183
168
 
184
169
  recent_articles(article_id, article_title, article_published_on, article_url) AS (
185
170
  SELECT id, title, published_on, url
186
171
  FROM articles
187
172
  RIGHT JOIN settings
188
- WHERE published_on > COALESCE(?1, settings.default_min_published_on)
173
+ WHERE published_on > settings.min_published_on
189
174
  ),
190
175
 
191
176
  tags_by_article(article_id, tags) AS (
@@ -204,7 +189,7 @@ JOIN tags_by_article USING(article_id),
204
189
  WHERE EXISTS (
205
190
  SELECT 1
206
191
  FROM json_each(tags)
207
- WHERE json_each.value LIKE ?2 OR ?2 IS NULL
192
+ WHERE json_each.value LIKE :tag OR :tag IS NULL
208
193
  )
209
194
  GROUP BY recent_articles.article_id
210
195
  ORDER BY recent_articles.article_published_on
@@ -251,28 +236,31 @@ AppQuery[:recent_articles].select_all.entries
251
236
  ...
252
237
  ]
253
238
 
254
- # we can provide a different cut off date via binds^1:
255
- AppQuery[:recent_articles].select_all(binds: [1.month.ago]).entries
239
+ # we can provide a different cut off date via binds:
240
+ AppQuery[:recent_articles].select_all(binds: {since: 1.month.ago}).entries
256
241
 
257
- 1) note that SQLite can deal with unbound parameters, i.e. when no binds are provided it assumes null for
258
- $1 and $2 (which our query can deal with).
259
- For Postgres you would always need to provide 2 values, e.g. `binds: [nil, nil]`.
242
+ # NOTE: by default the binds get initialized with nil, e.g. for this example {since: nil, tag: nil}
243
+ # This prevents you from having to provide all binds every time. Default values are put in the SQL (via COALESCE).
260
244
  ```
261
245
 
262
- We can also dig deeper by query-ing the result, i.e. the CTE `_`:
246
+ We can also dig deeper by query-ing the result, i.e. the CTE `:_`:
263
247
 
264
248
  ```ruby
265
- AppQuery[:recent_articles].select_one(select: "select count(*) as cnt from _")
249
+ AppQuery[:recent_articles].select_one("select count(*) as cnt from :_")
266
250
  # => {"cnt" => 13}
267
251
 
268
252
  # For these kind of aggregate queries, we're only interested in the value:
269
- AppQuery[:recent_articles].select_value(select: "select count(*) from _")
253
+ AppQuery[:recent_articles].select_value("select count(*) from :_")
270
254
  # => 13
255
+
256
+ # but there's also the shorthand #count (which takes a sub-select):
257
+ AppQuery[:recent_articles].count #=> 13
258
+ AppQuery[:recent_articles].count(binds: {since: 0}) #=> 275
271
259
  ```
272
260
 
273
261
  Use `AppQuery#with_select` to get a new AppQuery-instance with the rewritten SQL:
274
262
  ```ruby
275
- puts AppQuery[:recent_articles].with_select("select * from _")
263
+ puts AppQuery[:recent_articles].with_select("select id from :_")
276
264
  ```
277
265
 
278
266
 
@@ -280,15 +268,15 @@ puts AppQuery[:recent_articles].with_select("select * from _")
280
268
 
281
269
  You can select from a CTE similarly:
282
270
  ```ruby
283
- AppQuery[:recent_articles].select_all(select: "SELECT * FROM tags_by_article")
271
+ AppQuery[:recent_articles].select_all("SELECT * FROM tags_by_article")
284
272
  # => [{"article_id"=>1, "tags"=>"[\"release:pre\",\"release:patch\",\"release:1x\"]"},
285
273
  ...]
286
274
 
287
275
  # NOTE how the tags are json strings. Casting allows us to turn these into proper arrays^1:
288
276
  types = {"tags" => ActiveRecord::Type::Json.new}
289
- AppQuery[:recent_articles].select_all(select: "SELECT * FROM tags_by_article", cast: types)
277
+ AppQuery[:recent_articles].select_all("SELECT * FROM tags_by_article", cast: types)
290
278
 
291
- 1) PostgreSQL, unlike SQLite, has json and array types. Just casting suffices:
279
+ 1) unlike SQLite, PostgreSQL has json and array types. Just casting suffices:
292
280
  AppQuery("select json_build_object('a', 1, 'b', true)").select_one(cast: true)
293
281
  # => {"json_build_object"=>{"a"=>1, "b"=>true}}
294
282
  ```
@@ -297,7 +285,7 @@ Using the methods `(prepend|append|replace)_cte`, we can rewrite the query beyon
297
285
 
298
286
  ```ruby
299
287
  AppQuery[:recent_articles].replace_cte(<<~SQL).select_all.entries
300
- settings(default_min_published_on) as (
288
+ settings(min_published_on) as (
301
289
  values(datetime('now', '-12 months'))
302
290
  )
303
291
  SQL
@@ -309,10 +297,10 @@ You could even mock existing tables (using PostgreSQL):
309
297
  sample_articles = [{id: 1, title: "Some title", published_on: 3.months.ago},
310
298
  {id: 2, title: "Another title", published_on: 1.months.ago}]
311
299
  # show the provided cutoff date works
312
- AppQuery[:recent_articles].prepend_cte(<<-CTE).select_all(binds: [6.weeks.ago, nil, JSON[sample_articles]).entries
313
- articles AS (
314
- SELECT * from json_to_recordset($3) AS x(id int, title text, published_on timestamp)
315
- )
300
+ AppQuery[:recent_articles].prepend_cte(<<-CTE).select_all(binds: {since: 6.weeks.ago, articles: JSON[sample_articles]}).entries
301
+ articles AS (
302
+ SELECT * from json_to_recordset(:articles) AS x(id int, title text, published_on timestamp)
303
+ )
316
304
  CTE
317
305
  ```
318
306
 
@@ -332,7 +320,7 @@ require "rails_helper"
332
320
  RSpec.describe "AppQuery reports/weekly", type: :query, default_binds: [] do
333
321
  describe "CTE articles" do
334
322
  specify do
335
- expect(described_query.select_all(select: "select * from :cte")).to \
323
+ expect(described_query.select_all("select * from :cte")).to \
336
324
  include(a_hash_including("article_id" => 1))
337
325
 
338
326
  # short version: query, cte and select are all implied from descriptions
@@ -350,225 +338,10 @@ There's some sugar:
350
338
  When doing `select_all`, you can rewrite the `SELECT` of the query by passing `select`. There's no need to use the full name of the CTE as the spec-description contains the name (i.e. "articles" in "CTE articles").
351
339
  - default_binds
352
340
  The `binds`-value used when not explicitly provided.
353
- E.g. given a query with a where-clause `WHERE published_at > COALESCE($1::timestamp, NOW() - '3 month'::interval)`, when setting `defaults_binds: [nil]` then `select_all` works like `select_all(binds: [nil])`.
354
-
355
- ## 💎 API Doc 💎
356
-
357
- ### generic
358
-
359
- <details>
360
- <summary><code>AppQuery(sql) ⇒ AppQuery::Q</code></summary>
361
-
362
- ### Examples
363
-
364
- ```ruby
365
- AppQuery("some sql")
366
- ```
367
- </details>
368
-
369
- ### module AppQuery
370
-
371
- <details>
372
- <summary><code>AppQuery[query_name] ⇒ AppQuery::Q</code></summary>
373
-
374
- ### Examples
375
-
376
- ```ruby
377
- AppQuery[:recent_articles]
378
- AppQuery["export/articles"]
379
- ```
380
-
381
- </details>
382
-
383
- <details>
384
- <summary><code>AppQuery.configure {|Configuration| ... } ⇒ void </code></summary>
385
-
386
- Configure AppQuery.
387
-
388
- ### Examples
389
-
390
- ```ruby
391
- AppQuery.configure do |cfg|
392
- cfg.query_path = "db/queries" # default: "app/queries"
393
- end
394
- ```
395
-
396
- </details>
397
-
398
- <details>
399
- <summary><code>AppQuery.configuration ⇒ AppQuery::Configuration </code></summary>
400
-
401
- Get configuration
402
-
403
- ### Examples
404
-
405
- ```ruby
406
- AppQuery.configure do |cfg|
407
- cfg.query_path = "db/queries" # default: "app/queries"
408
- end
409
- AppQuery.configuration
410
- ```
411
-
412
- </details>
413
-
414
- ### class AppQuery::Q
415
-
416
- Instantiate via `AppQuery(sql)` or `AppQuery[:query_file]`.
417
-
418
- <details>
419
- <summary><code>AppQuery::Q#cte_names ⇒ [Array< String >] </code></summary>
420
-
421
- Returns names of CTEs in query.
422
-
423
- ### Examples
424
-
425
- ```ruby
426
- AppQuery("select * from articles").cte_names # => []
427
- AppQuery("with foo as(select 1) select * from foo").cte_names # => ["foo"]
428
- ```
429
-
430
- </details>
431
-
432
- <details>
433
- <summary><code>AppQuery::Q#recursive? ⇒ Boolean </code></summary>
434
-
435
- Returns whether or not the WITH-clause is recursive or not.
436
-
437
- ### Examples
438
341
 
439
- ```ruby
440
- AppQuery("select * from articles").recursive? # => false
441
- AppQuery("with recursive foo as(select 1) select * from foo") # => true
442
- ```
443
-
444
- </details>
445
-
446
- <details>
447
- <summary><code>AppQuery::Q#select ⇒ String </code></summary>
448
-
449
- Returns select-part of the query. When using CTEs, this will be `<select>` in a query like `with foo as (select 1) <select>`.
450
-
451
- ### Examples
452
-
453
- ```ruby
454
- AppQuery("select * from articles") # => "select * from articles"
455
- AppQuery("with foo as(select 1) select * from foo") # => "select * from foo"
456
- ```
457
-
458
- </details>
459
-
460
- #### query execution
461
-
462
- <details>
463
- <summary><code>AppQuery::Q#select_all(select: nil, binds: [], cast: false) ⇒ AppQuery::Result</code></summary>
464
-
465
- `select` replaces the existing select. The existing select is wrapped in a CTE named `_`.
466
- `binds` array with values for any (positional) placeholder in the query.
467
- `cast` boolean or `Hash` indicating whether or not (and how) to cast. E.g. `{"some_column" => ActiveRecord::Type::Date.new}`.
468
-
469
- ### Examples
470
-
471
- ```ruby
472
- # SQLite
473
- aq = AppQuery(<<~SQL)
474
- with data(id, title) as (
475
- values('1', 'Some title'),
476
- ('2', 'Another title')
477
- )
478
- select * from data
479
- where id=?1 or ?1 is null
480
- SQL
481
-
482
- # selecting from the select
483
- aq.select_all(select: "select * from _ where id > 1").entries #=> [{...}]
484
-
485
- # selecting from a CTE
486
- aq.select_all(select: "select id from data").entries
487
-
488
- # casting
489
- aq.select_all(select: "select id from data", cast: {"id" => ActiveRecord::Type::Integer.new})
490
-
491
- # binds
492
- aq.select_all(binds: ['2'])
493
- ```
494
-
495
- </details>
496
-
497
- <details>
498
- <summary><code>AppQuery::Q#select_one(select: nil, binds: [], cast: false) ⇒ AppQuery::Result </code></summary>
499
-
500
- First result from `AppQuery::Q#select_all`.
501
-
502
- See examples from `AppQuery::Q#select_all`.
503
-
504
- </details>
505
-
506
- <details>
507
- <summary><code>AppQuery::Q#select_value(select: nil, binds: [], cast: false) ⇒ AppQuery::Result </code></summary>
508
-
509
- First value from `AppQuery::Q#select_one`. Typically for selects like `select count(*) ...`, `select min(article_published_on) ...`.
342
+ ## API Documentation
510
343
 
511
- See examples from `AppQuery::Q#select_all`.
512
-
513
- </details>
514
-
515
- #### query rewriting
516
-
517
- <details>
518
- <summary><code>AppQuery::Q#with_select(sql) ⇒ AppQuery::Q</code></summary>
519
-
520
- Returns new instance with provided select. The existing select is available via CTE `_`.
521
-
522
- ### Examples
523
-
524
- ```ruby
525
- puts AppQuery("select 1").with_select("select 2")
526
- WITH _ as (
527
- select 1
528
- )
529
- select 2
530
- ```
531
-
532
- </details>
533
-
534
- <details>
535
- <summary><code>AppQuery::Q#prepend_cte(sql) ⇒ AppQuery::Q</code></summary>
536
-
537
- Returns new instance with provided CTE.
538
-
539
- ### Examples
540
-
541
- ```ruby
542
- query.prepend_cte("foo as (values(1, 'Some article'))").cte_names # => ["foo", "existing_cte"]
543
- ```
544
-
545
- </details>
546
-
547
- <details>
548
- <summary><code>AppQuery::Q#append_cte(sql) ⇒ AppQuery::Q</code></summary>
549
-
550
- Returns new instance with provided CTE.
551
-
552
- ### Examples
553
-
554
- ```ruby
555
- query.append_cte("foo as (values(1, 'Some article'))").cte_names # => ["existing_cte", "foo"]
556
- ```
557
-
558
- </details>
559
-
560
- <details>
561
- <summary><code>AppQuery::Q#replace_cte(sql) ⇒ AppQuery::Q</code></summary>
562
-
563
- Returns new instance with replaced CTE. Raises `ArgumentError` when CTE does not already exist.
564
-
565
- ### Examples
566
-
567
- ```ruby
568
- query.replace_cte("recent_articles as (select values(1, 'Some article'))")
569
- ```
570
-
571
- </details>
344
+ See the [YARD documentation](https://eval.github.io/appquery/) for the full API reference.
572
345
 
573
346
  ## Compatibility
574
347