wowsql-sdk 3.0.2 → 3.1.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.
data/lib/wowsql/table.rb CHANGED
@@ -1,169 +1,182 @@
1
1
  require_relative 'query_builder'
2
2
 
3
3
  module WOWSQL
4
- # Table interface for database operations.
4
+ # Table interface for direct PostgREST operations.
5
+ #
6
+ # All mutations use PostgREST native query parameters (?id=eq.val) and
7
+ # Prefer headers (return=representation, resolution=merge-duplicates).
8
+ #
9
+ # @example
10
+ # user = client.table("users").get_by_id("uuid-here")
11
+ # result = client.table("users").create({ email: "a@b.com", name: "Alice" })
5
12
  class Table
6
13
  def initialize(client, table_name)
7
- @client = client
14
+ @client = client
8
15
  @table_name = table_name
9
16
  end
10
17
 
11
- # Start a query with column selection.
12
- #
13
- # @param columns [Array<String>] Column(s) to select
14
- # @return [QueryBuilder] QueryBuilder for chaining
18
+ # ── Query builder shortcuts ───────────────────────────────────
19
+
15
20
  def select(*columns)
16
21
  QueryBuilder.new(@client, @table_name).select(*columns)
17
22
  end
18
23
 
19
- # Start a query with a filter.
20
- #
21
- # @param column [String, Hash] Column name or filter hash
22
- # @param operator [String, nil] Operator
23
- # @param value [Object] Filter value
24
- # @param logical_op [String] "AND" or "OR"
25
- # @return [QueryBuilder] QueryBuilder for chaining
26
24
  def filter(column, operator = nil, value = nil, logical_op: 'AND')
27
25
  QueryBuilder.new(@client, @table_name).filter(column, operator, value, logical_op: logical_op)
28
26
  end
29
27
 
30
- # Get all records with optional filters.
28
+ def eq(column, value)
29
+ QueryBuilder.new(@client, @table_name).eq(column, value)
30
+ end
31
+
32
+ def neq(column, value)
33
+ QueryBuilder.new(@client, @table_name).neq(column, value)
34
+ end
35
+
36
+ def gt(column, value)
37
+ QueryBuilder.new(@client, @table_name).gt(column, value)
38
+ end
39
+
40
+ def gte(column, value)
41
+ QueryBuilder.new(@client, @table_name).gte(column, value)
42
+ end
43
+
44
+ def lt(column, value)
45
+ QueryBuilder.new(@client, @table_name).lt(column, value)
46
+ end
47
+
48
+ def lte(column, value)
49
+ QueryBuilder.new(@client, @table_name).lte(column, value)
50
+ end
51
+
52
+ def order_by(column, direction = 'asc')
53
+ QueryBuilder.new(@client, @table_name).order_by(column, direction)
54
+ end
55
+
56
+ def limit(n)
57
+ QueryBuilder.new(@client, @table_name).limit(n)
58
+ end
59
+
60
+ def offset(n)
61
+ QueryBuilder.new(@client, @table_name).offset(n)
62
+ end
63
+
64
+ # ── Read ─────────────────────────────────────────────────────
65
+
66
+ # Retrieve all rows (with optional filter options forwarded to QueryBuilder#get).
31
67
  #
32
- # @param options [Hash, nil] Query options
33
- # @return [Hash] Query response
68
+ # @param options [Hash, nil] Reserved for compatibility; use chained methods instead
69
+ # @return [Hash] { data: Array, count: Integer, total: Integer, limit: Integer, offset: Integer }
34
70
  def get(options = nil)
35
71
  QueryBuilder.new(@client, @table_name).get(options)
36
72
  end
37
73
 
38
- # Get a single record by ID.
74
+ # Retrieve a single row by primary key.
39
75
  #
40
- # @param record_id [Object] Record ID
41
- # @return [Hash] Record data
76
+ # @param record_id [String] Row UUID
77
+ # @return [Hash] Row data
42
78
  def get_by_id(record_id)
43
- @client.request('GET', "/#{@table_name}/#{record_id}", nil, nil)
79
+ result = @client.request(
80
+ 'GET', "/#{@table_name}",
81
+ { 'id' => "eq.#{record_id}" },
82
+ nil,
83
+ 'Prefer' => 'return=representation'
84
+ )
85
+ data = normalise_data(result)
86
+ data.first
44
87
  end
45
88
 
46
- # Create a new record.
89
+ # ── Write ────────────────────────────────────────────────────
90
+
91
+ # Insert a single row and return the created record.
47
92
  #
48
- # @param data [Hash] Record data
49
- # @return [Hash] Create response with new record ID
93
+ # @param data [Hash] Column→value map
94
+ # @return [Hash] Created row
50
95
  def create(data)
51
- @client.request('POST', "/#{@table_name}", nil, data)
96
+ result = @client.request(
97
+ 'POST', "/#{@table_name}", nil, data,
98
+ 'Prefer' => 'return=representation'
99
+ )
100
+ normalise_data(result).first || {}
52
101
  end
53
102
 
54
- # Insert a new record (alias for create).
55
- #
56
- # @param data [Hash] Record data
57
- # @return [Hash] Create response with new record ID
58
- def insert(data)
59
- create(data)
60
- end
103
+ alias insert create
61
104
 
62
- # Insert multiple records.
105
+ # Insert multiple rows.
63
106
  #
64
- # Attempts a single batch POST first. Falls back to individual
65
- # inserts if the server does not support batch creation.
66
- #
67
- # @param records [Array<Hash>] List of record hashes
68
- # @return [Array<Hash>] List of create responses
107
+ # @param records [Array<Hash>] List of row hashes
108
+ # @return [Array<Hash>] Created rows
69
109
  def bulk_insert(records)
70
110
  return [] if records.nil? || records.empty?
71
111
 
72
- begin
73
- result = @client.request('POST', "/#{@table_name}", nil, records)
74
- result.is_a?(Array) ? result : [result]
75
- rescue StandardError
76
- records.map { |record| create(record) }
77
- end
112
+ result = @client.request(
113
+ 'POST', "/#{@table_name}", nil, records,
114
+ 'Prefer' => 'return=representation'
115
+ )
116
+ normalise_data(result)
78
117
  end
79
118
 
80
119
  # Insert or update based on conflict column.
81
120
  #
82
- # Uses PostgreSQL ON CONFLICT (upsert). Falls back to
83
- # get-then-insert/update when the backend doesn't expose
84
- # a native upsert endpoint.
85
- #
86
- # @param data [Hash] Record data (must include the conflict column)
87
- # @param on_conflict [String] Column to check for conflicts (default: "id")
88
- # @return [Hash] Create or update response
121
+ # @param data [Hash] Row data (must include the conflict column value)
122
+ # @param on_conflict [String] Column to detect conflicts on (default: "id")
123
+ # @return [Hash] Upserted row
89
124
  def upsert(data, on_conflict: 'id')
90
- conflict_value = data[on_conflict] || data[on_conflict.to_sym]
91
- return create(data) if conflict_value.nil?
92
-
93
- existing = QueryBuilder.new(@client, @table_name).eq(on_conflict, conflict_value).first
94
- if existing
95
- update_data = data.reject { |k, _| k.to_s == on_conflict }
96
- if update_data.empty?
97
- { 'message' => 'No changes', 'affected_rows' => 0 }
98
- else
99
- update(conflict_value, update_data)
100
- end
101
- else
102
- create(data)
103
- end
125
+ result = @client.request(
126
+ 'POST', "/#{@table_name}", nil, data,
127
+ 'Prefer' => 'return=representation,resolution=merge-duplicates',
128
+ 'on-conflict-column' => on_conflict
129
+ )
130
+ normalise_data(result).first || {}
104
131
  end
105
132
 
106
- # Update a record by ID.
133
+ # Update a row by ID.
107
134
  #
108
- # @param record_id [Object] Record ID
109
- # @param data [Hash] Data to update
110
- # @return [Hash] Update response
135
+ # @param record_id [String] Row UUID
136
+ # @param data [Hash] Columns to update
137
+ # @return [Hash] Updated row
111
138
  def update(record_id, data)
112
- @client.request('PATCH', "/#{@table_name}/#{record_id}", nil, data)
139
+ result = @client.request(
140
+ 'PATCH', "/#{@table_name}",
141
+ { 'id' => "eq.#{record_id}" },
142
+ data,
143
+ 'Prefer' => 'return=representation'
144
+ )
145
+ normalise_data(result).first || {}
113
146
  end
114
147
 
115
- # Delete a record by ID.
148
+ # Delete a row by ID.
116
149
  #
117
- # @param record_id [Object] Record ID
118
- # @return [Hash] Delete response
150
+ # @param record_id [String] Row UUID
151
+ # @return [Hash] Deleted row
119
152
  def delete(record_id)
120
- @client.request('DELETE', "/#{@table_name}/#{record_id}", nil, nil)
121
- end
122
-
123
- # ── Convenience shortcuts ──────────────────────────────────
124
-
125
- def eq(column, value)
126
- QueryBuilder.new(@client, @table_name).eq(column, value)
127
- end
128
-
129
- def neq(column, value)
130
- QueryBuilder.new(@client, @table_name).neq(column, value)
131
- end
132
-
133
- def gt(column, value)
134
- QueryBuilder.new(@client, @table_name).gt(column, value)
135
- end
136
-
137
- def gte(column, value)
138
- QueryBuilder.new(@client, @table_name).gte(column, value)
139
- end
140
-
141
- def lt(column, value)
142
- QueryBuilder.new(@client, @table_name).lt(column, value)
153
+ result = @client.request(
154
+ 'DELETE', "/#{@table_name}",
155
+ { 'id' => "eq.#{record_id}" },
156
+ nil,
157
+ 'Prefer' => 'return=representation'
158
+ )
159
+ normalise_data(result).first || {}
143
160
  end
144
161
 
145
- def lte(column, value)
146
- QueryBuilder.new(@client, @table_name).lte(column, value)
147
- end
162
+ # ── Aggregates / pagination ───────────────────────────────────
148
163
 
149
- def order_by(column, direction = 'asc')
150
- QueryBuilder.new(@client, @table_name).order_by(column, direction)
151
- end
152
-
153
- # Get total record count for this table.
154
- #
155
- # @return [Integer]
156
164
  def count
157
165
  QueryBuilder.new(@client, @table_name).count
158
166
  end
159
167
 
160
- # Paginate all records in this table.
161
- #
162
- # @param page [Integer] Page number (1-indexed)
163
- # @param per_page [Integer] Records per page
164
- # @return [Hash]
165
168
  def paginate(page: 1, per_page: 20)
166
169
  QueryBuilder.new(@client, @table_name).paginate(page: page, per_page: per_page)
167
170
  end
171
+
172
+ private
173
+
174
+ def normalise_data(result)
175
+ case result
176
+ when Array then result
177
+ when Hash then result['data'] || result['rows'] || (result.key?('id') ? [result] : [])
178
+ else []
179
+ end
180
+ end
168
181
  end
169
182
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wowsql-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WOWSQL Team