@@ -50,11 +50,14 @@ function M.is_datetime_column(col_meta)
5050end
5151
5252function M .is_enum_column (col_meta )
53- return col_meta and col_meta .enum_values and # col_meta .enum_values > 0
53+ return col_meta and col_meta .enum_values and # col_meta .enum_values > 0 or false
5454end
5555
5656function M .is_editable_field (col_meta )
5757 if not col_meta then return false end
58+ if col_meta .user_defined then
59+ return false
60+ end
5861 if col_meta .primary_key and col_meta .default then
5962 return true
6063 end
7477--- Parse a user input string into a typed value suitable for SQL.
7578--- Handles: JSON, UUID, datetime, numbers, booleans, NULL.
7679function M .parse_value (input , old_val )
77- if input == " " or input == " (NULL)" then return vim .NIL end
78- if input == " null" then return vim .NIL end
80+ if input == " (NULL)" then return vim .NIL end
81+ if input == " null" or input == " NULL" then return vim .NIL end
82+ if input == " " then
83+ return old_val ~= nil and vim .NIL or nil
84+ end
85+ if input == " ''" then return " " end
7986
8087 -- Expressions (computed in SQL)
8188 if input :match (" ^__expr:" ) then return input end
@@ -123,6 +130,10 @@ function M.validate_value(value, col_meta)
123130 end
124131
125132 if type (value ) == " number" then
133+ if is_type (ctype , " boolean" ) then
134+ if value == 1 or value == 0 then return true end
135+ return false , " Only 0 and 1 allowed for boolean column"
136+ end
126137 if not is_type (ctype , " numeric" ) then
127138 return false , " Cannot assign number to " .. (col_meta .ctype or " unknown" ) .. " column"
128139 end
@@ -136,9 +147,22 @@ function M.validate_value(value, col_meta)
136147 if type (value ) == " string" then
137148 if value :match (" ^__expr:" ) then return true end
138149 if is_type (ctype , " uuid" ) then
139- if not value :match (" ^[0-9a-fA-F%-]+$" ) then
150+ local parts = { value :match (" ^(%x%x%x%x%x%x%x%x)-(%x%x%x%x)-(%x%x%x%x)-(%x%x%x%x)-(%x%x%x%x%x%x%x%x%x%x%x%x)$" ) }
151+ if not parts or # parts ~= 5 then
140152 return false , " Invalid UUID format"
141153 end
154+ return true
155+ end
156+ if is_type (ctype , " integer" ) or is_type (ctype , " numeric" ) then
157+ return false , " Cannot assign string to " .. (col_meta .ctype or " unknown" ) .. " column"
158+ end
159+ if is_type (ctype , " boolean" ) then
160+ return false , " Cannot assign string to boolean column"
161+ end
162+ if is_type (ctype , " date" ) then
163+ if not value :match (" ^%d" ) then
164+ return false , " Invalid date format"
165+ end
142166 end
143167 return true
144168 end
@@ -158,7 +182,7 @@ function M.create_edit_state()
158182 deleted_rows = {},
159183 added_rows = {},
160184 dirty = false ,
161- errors = {},
185+ cell_errors = {},
162186 }
163187end
164188
169193--- @param old_val any Previous value
170194--- @param new_val any New value
171195function M .track_cell_edit (es , row_key , col , old_val , new_val )
196+ if new_val == old_val then
197+ es .modified_cells [row_key ] = nil
198+ if not next (es .modified_cells ) then
199+ es .dirty = false
200+ end
201+ return
202+ end
203+ local row_idx = tonumber (row_key :match (" ^(%d+):" ))
204+ if row_idx and es .deleted_rows [row_idx ] then
205+ return
206+ end
172207 es .modified_cells [row_key ] = { col = col , old_val = old_val , new_val = new_val }
173208 es .dirty = true
174209end
@@ -179,14 +214,20 @@ end
179214function M .track_row_delete (es , row_idx )
180215 es .deleted_rows [row_idx ] = true
181216 es .dirty = true
217+ for k , _ in pairs (es .modified_cells ) do
218+ local r = tonumber (k :match (" ^(%d+):" ))
219+ if r == row_idx then
220+ es .modified_cells [k ] = nil
221+ end
222+ end
182223end
183224
184225--- Track a row addition.
185226--- @param es table Edit state
186227--- @param row_data table Row data
187228--- @param row_idx number Row index
188229function M .track_row_add (es , row_data , row_idx )
189- table.insert (es .added_rows , { row_data = row_data , row_idx = row_idx })
230+ table.insert (es .added_rows , { data = row_data , row_idx = row_idx })
190231 es .dirty = true
191232end
192233
@@ -200,33 +241,33 @@ end
200241
201242--- Get a summary of pending changes for display.
202243function M .get_edit_summary (es )
203- if not es then return " " end
204- local parts = {}
205- local cell_count = 0
206- for _ in pairs (es .modified_cells ) do cell_count = cell_count + 1 end
207- if cell_count > 0 then table.insert (parts , cell_count .. " cells" ) end
208- local del_count = 0
209- for _ in pairs (es .deleted_rows ) do del_count = del_count + 1 end
210- if del_count > 0 then table.insert (parts , del_count .. " deleted" ) end
211- if # es .added_rows > 0 then table.insert (parts , # es .added_rows .. " added" ) end
212- return table.concat (parts , " , " )
244+ if not es then return { updates = 0 , inserts = 0 , deletes = 0 } end
245+ local ups = 0
246+ for _ in pairs (es .modified_cells ) do ups = ups + 1 end
247+ local dels = 0
248+ for _ in pairs (es .deleted_rows ) do dels = dels + 1 end
249+ return { updates = ups , inserts = # es .added_rows , deletes = dels }
213250end
214251
215252--- Count pending changes (for display).
216253function M .count_pending_changes (es )
217- if not es or not es .dirty then return 0 end
218- local count = 0
219- for _ in pairs (es .modified_cells ) do count = count + 1 end
220- for _ in pairs ( es . deleted_rows ) do count = count + 1 end
221- count = count + # es . added_rows
222- return count
254+ if not es or not es .dirty then return { modified = 0 , deleted = 0 , added = 0 } end
255+ local mod = 0
256+ for _ in pairs (es .modified_cells ) do mod = mod + 1 end
257+ local del = 0
258+ for _ in pairs ( es . deleted_rows ) do del = del + 1 end
259+ return { modified = mod , deleted = del , added = # es . added_rows }
223260end
224261
225262--- Return a short text representation of pending changes.
226263function M .pending_changes_text (es )
227- if not es or not es .dirty or not M .has_pending_changes (es ) then return " " end
228- local count = M .count_pending_changes (es )
229- return " (" .. count .. " change" .. (count ~= 1 and " s" or " " ) .. " )"
264+ if not es or not es .dirty or not M .has_pending_changes (es ) then return nil end
265+ local counts = M .count_pending_changes (es )
266+ local parts = {}
267+ if counts .modified > 0 then table.insert (parts , " ~" .. counts .modified ) end
268+ if counts .added > 0 then table.insert (parts , " +" .. counts .added ) end
269+ if counts .deleted > 0 then table.insert (parts , " -" .. counts .deleted ) end
270+ return table.concat (parts , " " )
230271end
231272
232273--- Reset edit state, clearing all pending changes.
@@ -236,21 +277,21 @@ function M.reset_edit_state(es)
236277 es .deleted_rows = {}
237278 es .added_rows = {}
238279 es .dirty = false
239- es .errors = {}
280+ es .cell_errors = {}
240281end
241282
242283--- Clear a cell error.
243284function M .clear_cell_error (es , row_key )
244- if es and es .errors then
245- es .errors [row_key ] = nil
285+ if es and es .cell_errors then
286+ es .cell_errors [row_key ] = nil
246287 end
247288end
248289
249290--- Set a cell error.
250291function M .set_cell_error (es , row_key , msg )
251292 if not es then return end
252- if not es .errors then es .errors = {} end
253- es .errors [row_key ] = msg
293+ if not es .cell_errors then es .cell_errors = {} end
294+ es .cell_errors [row_key ] = msg
254295end
255296
256297---- -----------------------------------------------------------------------
261302--- @param val table JSON value
262303--- @return string
263304function M .format_json_input (val )
305+ if type (val ) == " string" then return val end
264306 local ok , str = pcall (vim .json .encode , val )
265307 if ok then
266308 local no_esc = str :gsub (' \\ "' , ' "' )
0 commit comments