Class: Tina4::DatabaseResult
- Inherits:
-
Object
- Object
- Tina4::DatabaseResult
- Includes:
- Enumerable
- Defined in:
- lib/tina4/database_result.rb
Instance Attribute Summary collapse
-
#affected_rows ⇒ Object
readonly
Returns the value of attribute affected_rows.
-
#columns ⇒ Object
readonly
Returns the value of attribute columns.
-
#count ⇒ Object
readonly
Returns the value of attribute count.
-
#error ⇒ Object
readonly
Returns the value of attribute error.
-
#last_id ⇒ Object
readonly
Returns the value of attribute last_id.
-
#limit ⇒ Object
readonly
Returns the value of attribute limit.
-
#offset ⇒ Object
readonly
Returns the value of attribute offset.
-
#records ⇒ Object
readonly
Returns the value of attribute records.
-
#sql ⇒ Object
readonly
Returns the value of attribute sql.
Instance Method Summary collapse
-
#[](*args) ⇒ Object
Index / slice access into the result rows.
-
#column_info ⇒ Object
Return column metadata for the query's table.
- #each(&block) ⇒ Object
- #empty? ⇒ Boolean
- #first ⇒ Object
-
#initialize(records = [], sql: "", columns: [], count: nil, limit: 0, offset: 0, affected_rows: 0, last_id: nil, error: nil, db: nil) ⇒ DatabaseResult
constructor
limitis the row cap ACTUALLY APPLIED to the statement that produced these records — what Database#fetch appended — and0means none was (an explicit no-limit read, SQL carrying its own trailing LIMIT, or a write). - #last ⇒ Object
- #length ⇒ Object
- #size ⇒ Object
- #success? ⇒ Boolean
- #to_array ⇒ Object (also: #to_a)
-
#to_ary ⇒ Object
Implicit array coercion, so a DatabaseResult can be splatted and used anywhere an Array is expected (
a, b = result,[*result]). - #to_crud(table_name: "data", primary_key: "id", editable: true) ⇒ Object
- #to_csv(separator: ",", headers: true) ⇒ Object
- #to_json(*_args) ⇒ Object
-
#to_paginate(*args, **kwargs) ⇒ Object
Describe the page this result already IS.
Constructor Details
#initialize(records = [], sql: "", columns: [], count: nil, limit: 0, offset: 0, affected_rows: 0, last_id: nil, error: nil, db: nil) ⇒ DatabaseResult
limit is the row cap ACTUALLY APPLIED to the statement that produced
these records — what Database#fetch appended — and 0 means none was
(an explicit no-limit read, SQL carrying its own trailing LIMIT, or a
write). The default used to be 10: Database#fetch_direct never passed
limit:/offset:, so that untouched default was reported on EVERY fetch
whatever limit ran — and 10 is the stale v2 documentation number that the
buried PHP row-cap test also asserted as the cap. 0 says "no cap recorded"
instead of quietly naming a number nothing applied.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/tina4/database_result.rb', line 19 def initialize(records = [], sql: "", columns: [], count: nil, limit: 0, offset: 0, affected_rows: 0, last_id: nil, error: nil, db: nil) @records = records || [] @sql = sql @columns = columns.empty? && !@records.empty? ? @records.first.keys : columns @count = count || @records.length @limit = limit @offset = offset @affected_rows = affected_rows @last_id = last_id @error = error @db = db @column_info_cache = nil end |
Instance Attribute Details
#affected_rows ⇒ Object (readonly)
Returns the value of attribute affected_rows.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def affected_rows @affected_rows end |
#columns ⇒ Object (readonly)
Returns the value of attribute columns.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def columns @columns end |
#count ⇒ Object (readonly)
Returns the value of attribute count.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def count @count end |
#error ⇒ Object (readonly)
Returns the value of attribute error.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def error @error end |
#last_id ⇒ Object (readonly)
Returns the value of attribute last_id.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def last_id @last_id end |
#limit ⇒ Object (readonly)
Returns the value of attribute limit.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def limit @limit end |
#offset ⇒ Object (readonly)
Returns the value of attribute offset.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def offset @offset end |
#records ⇒ Object (readonly)
Returns the value of attribute records.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def records @records end |
#sql ⇒ Object (readonly)
Returns the value of attribute sql.
8 9 10 |
# File 'lib/tina4/database_result.rb', line 8 def sql @sql end |
Instance Method Details
#[](*args) ⇒ Object
Index / slice access into the result rows.
result[0] is documented (book ch5 §4 "Index Access"). Delegating
straight to the materialised rows means every Array subscript form
works — result[0], result[-1], result[1, 2] and
result[1..3] — and matches Python's DatabaseResult.__getitem__,
which forwards to its records list.
57 58 59 |
# File 'lib/tina4/database_result.rb', line 57 def [](*args) @records[*args] end |
#column_info ⇒ Object
Return column metadata for the query's table.
Lazy — only queries the database when explicitly called. Caches the result so subsequent calls return immediately without re-querying.
Returns an array of hashes with keys:
name, type, size, decimals, nullable, primary_key
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/tina4/database_result.rb', line 171 def column_info return @column_info_cache if @column_info_cache table = extract_table_from_sql if @db && table begin @column_info_cache = (table) return @column_info_cache rescue StandardError # Fall through to fallback end end @column_info_cache = fallback_column_info @column_info_cache end |
#each(&block) ⇒ Object
34 35 36 |
# File 'lib/tina4/database_result.rb', line 34 def each(&block) @records.each(&block) end |
#empty? ⇒ Boolean
46 47 48 |
# File 'lib/tina4/database_result.rb', line 46 def empty? @records.empty? end |
#first ⇒ Object
38 39 40 |
# File 'lib/tina4/database_result.rb', line 38 def first @records.first end |
#last ⇒ Object
42 43 44 |
# File 'lib/tina4/database_result.rb', line 42 def last @records.last end |
#length ⇒ Object
67 68 69 |
# File 'lib/tina4/database_result.rb', line 67 def length @count end |
#size ⇒ Object
71 72 73 |
# File 'lib/tina4/database_result.rb', line 71 def size @count end |
#success? ⇒ Boolean
75 76 77 |
# File 'lib/tina4/database_result.rb', line 75 def success? @error.nil? end |
#to_array ⇒ Object Also known as: to_a
79 80 81 82 83 |
# File 'lib/tina4/database_result.rb', line 79 def to_array @records.map do |record| record.is_a?(Hash) ? record : record.to_h end end |
#to_ary ⇒ Object
Implicit array coercion, so a DatabaseResult can be splatted and used
anywhere an Array is expected (a, b = result, [*result]).
63 64 65 |
# File 'lib/tina4/database_result.rb', line 63 def to_ary @records.dup end |
#to_crud(table_name: "data", primary_key: "id", editable: true) ⇒ Object
159 160 161 162 |
# File 'lib/tina4/database_result.rb', line 159 def to_crud(table_name: "data", primary_key: "id", editable: true) Tina4::Crud.generate_table(@records, table_name: table_name, primary_key: primary_key, editable: editable) end |
#to_csv(separator: ",", headers: true) ⇒ Object
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/tina4/database_result.rb', line 91 def to_csv(separator: ",", headers: true) return "" if @records.empty? lines = [] cols = @records.first.keys lines << cols.join(separator) if headers @records.each do |row| lines << cols.map { |c| escape_csv(row[c], separator) }.join(separator) end lines.join("\n") end |
#to_json(*_args) ⇒ Object
87 88 89 |
# File 'lib/tina4/database_result.rb', line 87 def to_json(*_args) JSON.generate(to_array) end |
#to_paginate(*args, **kwargs) ⇒ Object
Describe the page this result already IS. Takes NO arguments (ADR-0043).
Every field is derived from the query that produced this result:
per_page = the query's limit
page = floor(offset / limit) + 1
total = the true total for the filter (@count, from the COUNT probe
Database#fetch runs when it applied the limit), NEVER the
number of rows this page returned
total_pages = ceil(total / per_page)
records = the rows the query returned, VERBATIM, never re-sliced
limit/offset= the SQL limit/offset actually applied
The envelope is EXACTLY seven snake_case keys, identical in all four frameworks: records, total, page, per_page, total_pages, limit, offset. The old duplicate/camelCase spellings (data, count, totalPages, has_next, has_prev) are gone - a JSON key is data and does not change spelling by host language, so the same integer never ships twice under two names.
PASSING ANY ARGUMENT RAISES. A DatabaseResult holds no connection, so an argument could only re-slice rows already in memory and then report total_pages for pages it can never reach. To read page N, FETCH page N (limit + offset) and call this with no arguments. The removed page:/per_page: slicing mode is a hard error, never a silent reinterpretation - superseding the in-memory slice GitHub #106 asked for.
MEASURED 2026-08-05 on a real 250-row table read with limit=20 offset=40 (page 3 of 13): the old two-mode method re-sliced @records by the ABSOLUTE offset (40) against an array already only 20 long and returned ZERO records for a valid page, while still shipping a page number and total - an envelope that looked authoritative and was empty.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/tina4/database_result.rb', line 132 def to_paginate(*args, **kwargs) unless args.empty? && kwargs.empty? raise ArgumentError, "to_paginate takes no arguments (ADR-0043): it describes the page " \ "this result already IS, derived from the query that produced it. A " \ "DatabaseResult holds no connection, so an argument could only " \ "re-slice rows already in memory and lie about total_pages. To read " \ "another page, FETCH it - fetch(sql, limit: per_page, offset: " \ "(page - 1) * per_page) - then call to_paginate with no arguments." end per_page = @limit.to_i > 0 ? @limit.to_i : @records.size page = per_page > 0 ? (@offset.to_i / per_page) + 1 : 1 total = @count total_pages = per_page > 0 ? [1, (total.to_f / per_page).ceil].max : 1 { records: @records, total: total, page: page, per_page: per_page, total_pages: total_pages, limit: per_page, offset: @offset.to_i } end |