mirror of
https://gitlab.gnome.org/jpu/cambalache.git
synced 2025-06-25 00:02:51 -04:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4f7272b2f9 | ||
|
eea5ff18ea |
11
CHANGELOG.md
11
CHANGELOG.md
@ -8,6 +8,17 @@ packaging changes like new dependencies or build system changes.
|
|||||||
Cambalache used even/odd minor numbers to differentiate between stable and
|
Cambalache used even/odd minor numbers to differentiate between stable and
|
||||||
development releases.
|
development releases.
|
||||||
|
|
||||||
|
## 0.94.1
|
||||||
|
|
||||||
|
2024-11-26 - First bugfix release in the series!
|
||||||
|
|
||||||
|
- Fix issue with undoing a parent change.
|
||||||
|
- Fix workspace initialization error
|
||||||
|
- Fix warning on undo/redo message generation
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
|
||||||
|
- #253 "Error updating UI 1: gtk-builder-error-quark: .:8:1 Invalid object type 'AdwApplicationWindow' (6)"
|
||||||
|
|
||||||
## 0.94.0
|
## 0.94.0
|
||||||
|
|
||||||
|
@ -158,10 +158,15 @@ class CmbDB(GObject.GObject):
|
|||||||
def history_delete(self, table, table_pk):
|
def history_delete(self, table, table_pk):
|
||||||
self.execute(self.__history_commands[table]["DELETE"], table_pk)
|
self.execute(self.__history_commands[table]["DELETE"], table_pk)
|
||||||
|
|
||||||
def history_update(self, table, column, table_pk, values):
|
def history_update(self, table, columns, table_pk, values):
|
||||||
command = self.__history_commands[table]["UPDATE"].format(column=column)
|
update_command = self.__history_commands[table]["UPDATE"]
|
||||||
i = self.__table_column_mapping[table][column]
|
set_expression = f"({','.join(columns)}) = ({','.join(['?' for i in columns])})"
|
||||||
self.execute(command, [values[i]] + table_pk)
|
|
||||||
|
exp_vals = []
|
||||||
|
for col in columns:
|
||||||
|
exp_vals.append(values[self.__table_column_mapping[table][col]])
|
||||||
|
|
||||||
|
self.execute(update_command.format(set_expression=set_expression), exp_vals + table_pk)
|
||||||
|
|
||||||
def __create_history_triggers(self, c, table):
|
def __create_history_triggers(self, c, table):
|
||||||
# Get table columns
|
# Get table columns
|
||||||
@ -185,6 +190,7 @@ class CmbDB(GObject.GObject):
|
|||||||
"""
|
"""
|
||||||
self.__clear_history = clear_history
|
self.__clear_history = clear_history
|
||||||
|
|
||||||
|
# Collect Column info
|
||||||
i = 0
|
i = 0
|
||||||
column_mapping = {}
|
column_mapping = {}
|
||||||
for row in c.execute(f"PRAGMA table_info({table});"):
|
for row in c.execute(f"PRAGMA table_info({table});"):
|
||||||
@ -200,6 +206,28 @@ class CmbDB(GObject.GObject):
|
|||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
# Collect unique constraint indexes
|
||||||
|
unique_constraints_indexes = []
|
||||||
|
for row in c.execute(f"PRAGMA index_list({table});"):
|
||||||
|
n, name, is_unique, index_type, is_partial = row
|
||||||
|
if is_unique and index_type == "u":
|
||||||
|
unique_constraints_indexes.append(name)
|
||||||
|
|
||||||
|
# Collect unique constraints indexes with more than one non pk column
|
||||||
|
unique_constraints = []
|
||||||
|
for index_name in unique_constraints_indexes:
|
||||||
|
index_columns = []
|
||||||
|
|
||||||
|
for row in c.execute(f"PRAGMA index_info({index_name});"):
|
||||||
|
index_rank, table_rank, name = row
|
||||||
|
if name not in pk_columns:
|
||||||
|
index_columns.append(name)
|
||||||
|
|
||||||
|
if len(index_columns) > 1:
|
||||||
|
unique_constraints.append(index_columns)
|
||||||
|
|
||||||
|
unique_constraints_flat = [i for constraints in unique_constraints for i in constraints]
|
||||||
|
|
||||||
# Map column index to column name
|
# Map column index to column name
|
||||||
self.__table_column_mapping[table] = column_mapping
|
self.__table_column_mapping[table] = column_mapping
|
||||||
|
|
||||||
@ -215,7 +243,7 @@ class CmbDB(GObject.GObject):
|
|||||||
self.__history_commands[table] = {
|
self.__history_commands[table] = {
|
||||||
"DELETE": f"DELETE FROM {table} WHERE ({pkcolumns}) IS ({pkcolumns_format});",
|
"DELETE": f"DELETE FROM {table} WHERE ({pkcolumns}) IS ({pkcolumns_format});",
|
||||||
"INSERT": f"INSERT INTO {table} ({columns}) VALUES ({columns_format});",
|
"INSERT": f"INSERT INTO {table} ({columns}) VALUES ({columns_format});",
|
||||||
"UPDATE": f"UPDATE {table} SET {{column}} = ? WHERE ({pkcolumns}) IS ({pkcolumns_format});",
|
"UPDATE": f"UPDATE {table} SET {{set_expression}} WHERE ({pkcolumns}) IS ({pkcolumns_format});",
|
||||||
}
|
}
|
||||||
|
|
||||||
# INSERT Trigger
|
# INSERT Trigger
|
||||||
@ -249,8 +277,43 @@ class CmbDB(GObject.GObject):
|
|||||||
if len(pk_columns) == 0:
|
if len(pk_columns) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# UPDATE Trigger for each non PK column unique indexes
|
||||||
|
for columns in unique_constraints:
|
||||||
|
underscore_columns = "_".join(columns)
|
||||||
|
colon_columns = ",".join(columns)
|
||||||
|
new_columns = ",".join(f"NEW.{col}" for col in columns)
|
||||||
|
old_columns = ",".join(f"OLD.{col}" for col in columns)
|
||||||
|
string_columns = ",".join(f"'{col}'" for col in columns)
|
||||||
|
|
||||||
|
c.execute(
|
||||||
|
f"""
|
||||||
|
CREATE TRIGGER on_{table}_update_{underscore_columns} AFTER UPDATE OF {colon_columns} ON {table}
|
||||||
|
WHEN
|
||||||
|
({new_columns}) IS NOT ({old_columns}) AND {history_is_enabled} AND
|
||||||
|
(
|
||||||
|
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS NOT json_array({old_pk_values})
|
||||||
|
OR
|
||||||
|
(
|
||||||
|
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||||
|
IS NOT ('UPDATE', '{table}', json_array({string_columns}))
|
||||||
|
AND
|
||||||
|
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||||
|
IS NOT ('INSERT', '{table}', NULL)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
{clear_history};
|
||||||
|
INSERT INTO history (history_id, command, table_name, columns, table_pk, new_values, old_values)
|
||||||
|
VALUES ({history_next_seq}, 'UPDATE', '{table}', json_array({string_columns}), json_array({new_pk_values}), json_array({new_values}), json_array({old_values}));
|
||||||
|
END;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
# UPDATE Trigger for each non PK column
|
# UPDATE Trigger for each non PK column
|
||||||
for column in non_pk_columns:
|
for column in non_pk_columns:
|
||||||
|
if column in unique_constraints_flat:
|
||||||
|
continue
|
||||||
|
|
||||||
c.execute(
|
c.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TRIGGER on_{table}_update_{column} AFTER UPDATE OF {column} ON {table}
|
CREATE TRIGGER on_{table}_update_{column} AFTER UPDATE OF {column} ON {table}
|
||||||
@ -260,17 +323,17 @@ class CmbDB(GObject.GObject):
|
|||||||
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS NOT json_array({old_pk_values})
|
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS NOT json_array({old_pk_values})
|
||||||
OR
|
OR
|
||||||
(
|
(
|
||||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||||
IS NOT ('UPDATE', '{table}', '{column}')
|
IS NOT ('UPDATE', '{table}', json_array('{column}'))
|
||||||
AND
|
AND
|
||||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||||
IS NOT ('INSERT', '{table}', NULL)
|
IS NOT ('INSERT', '{table}', NULL)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
BEGIN
|
BEGIN
|
||||||
{clear_history};
|
{clear_history};
|
||||||
INSERT INTO history (history_id, command, table_name, column_name, table_pk, new_values, old_values)
|
INSERT INTO history (history_id, command, table_name, columns, table_pk, new_values, old_values)
|
||||||
VALUES ({history_next_seq}, 'UPDATE', '{table}', '{column}', json_array({new_pk_values}), json_array({new_values}), json_array({old_values}));
|
VALUES ({history_next_seq}, 'UPDATE', '{table}', json_array('{column}'), json_array({new_pk_values}), json_array({new_values}), json_array({old_values}));
|
||||||
END;
|
END;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@ -282,8 +345,8 @@ class CmbDB(GObject.GObject):
|
|||||||
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND
|
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND
|
||||||
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
|
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
|
||||||
AND
|
AND
|
||||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||||
IS ('UPDATE', '{table}', '{column}')
|
IS ('UPDATE', '{table}', json_array('{column}'))
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
|
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
|
||||||
END;
|
END;
|
||||||
@ -297,7 +360,7 @@ class CmbDB(GObject.GObject):
|
|||||||
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND
|
NEW.{column} IS NOT OLD.{column} AND {history_is_enabled} AND
|
||||||
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
|
(SELECT table_pk FROM history WHERE history_id = {history_seq}) IS json_array({old_pk_values})
|
||||||
AND
|
AND
|
||||||
(SELECT command, table_name, column_name FROM history WHERE history_id = {history_seq})
|
(SELECT command, table_name, columns FROM history WHERE history_id = {history_seq})
|
||||||
IS ('INSERT', '{table}', NULL)
|
IS ('INSERT', '{table}', NULL)
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
|
UPDATE history SET new_values=json_array({new_values}) WHERE history_id = {history_seq};
|
||||||
|
@ -335,7 +335,7 @@ class CmbObject(CmbBaseObject, Gio.ListModel):
|
|||||||
|
|
||||||
project.db.execute(
|
project.db.execute(
|
||||||
"UPDATE object SET parent_id=?, position=? WHERE ui_id=? AND object_id=?;",
|
"UPDATE object SET parent_id=?, position=? WHERE ui_id=? AND object_id=?;",
|
||||||
(new_parent_id, new_position, ui_id, object_id)
|
(new_parent_id, new_position or 0, ui_id, object_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update children positions in old parent
|
# Update children positions in old parent
|
||||||
|
@ -418,6 +418,9 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
return n_files
|
return n_files
|
||||||
|
|
||||||
def __selection_remove(self, obj):
|
def __selection_remove(self, obj):
|
||||||
|
if obj not in self.__selection:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.__selection.remove(obj)
|
self.__selection.remove(obj)
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -790,11 +793,12 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
c = self.db.cursor()
|
c = self.db.cursor()
|
||||||
|
|
||||||
# Get last command
|
# Get last command
|
||||||
command, range_id, table, column, table_pk, old_values, new_values = c.execute(
|
command, range_id, table, columns, table_pk, old_values, new_values = c.execute(
|
||||||
"SELECT command, range_id, table_name, column_name, table_pk, old_values, new_values FROM history WHERE history_id=?",
|
"SELECT command, range_id, table_name, columns, table_pk, old_values, new_values FROM history WHERE history_id=?",
|
||||||
(self.history_index, )
|
(self.history_index,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
|
columns = json.loads(columns) if columns else None
|
||||||
table_pk = json.loads(table_pk) if table_pk else None
|
table_pk = json.loads(table_pk) if table_pk else None
|
||||||
old_values = json.loads(old_values) if old_values else None
|
old_values = json.loads(old_values) if old_values else None
|
||||||
new_values = json.loads(new_values) if new_values else None
|
new_values = json.loads(new_values) if new_values else None
|
||||||
@ -814,7 +818,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
else:
|
else:
|
||||||
self.db.history_insert(table, new_values)
|
self.db.history_insert(table, new_values)
|
||||||
|
|
||||||
self.__undo_redo_update_insert_delete(c, undo, command, table, column, table_pk, old_values, new_values)
|
self.__undo_redo_update_insert_delete(c, undo, command, table, columns, table_pk, old_values, new_values)
|
||||||
elif command == "DELETE":
|
elif command == "DELETE":
|
||||||
if table == "object":
|
if table == "object":
|
||||||
parent, position = get_object_position(c, old_values)
|
parent, position = get_object_position(c, old_values)
|
||||||
@ -829,10 +833,10 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
else:
|
else:
|
||||||
self.db.history_delete(table, table_pk)
|
self.db.history_delete(table, table_pk)
|
||||||
|
|
||||||
self.__undo_redo_update_insert_delete(c, undo, command, table, column, table_pk, old_values, new_values)
|
self.__undo_redo_update_insert_delete(c, undo, command, table, columns, table_pk, old_values, new_values)
|
||||||
elif command == "UPDATE":
|
elif command == "UPDATE":
|
||||||
# Ignore parent_id since its changed together with position
|
# parent_id and position have to change together because their are part of a unique index
|
||||||
if update_objects is not None and table == "object" and column == "position":
|
if update_objects is not None and table == "object" and "position" in columns and "parent_id" in columns:
|
||||||
old_parent, old_position = get_object_position(c, old_values)
|
old_parent, old_position = get_object_position(c, old_values)
|
||||||
new_parent, new_position = get_object_position(c, new_values)
|
new_parent, new_position = get_object_position(c, new_values)
|
||||||
|
|
||||||
@ -848,11 +852,11 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
update_objects.append((old_parent, old_position, 1, 0))
|
update_objects.append((old_parent, old_position, 1, 0))
|
||||||
|
|
||||||
if undo:
|
if undo:
|
||||||
self.db.history_update(table, column, table_pk, old_values)
|
self.db.history_update(table, columns, table_pk, old_values)
|
||||||
else:
|
else:
|
||||||
self.db.history_update(table, column, table_pk, new_values)
|
self.db.history_update(table, columns, table_pk, new_values)
|
||||||
|
|
||||||
self.__undo_redo_update_update(c, undo, command, table, column, table_pk, old_values, new_values)
|
self.__undo_redo_update_update(c, undo, command, table, columns, table_pk, old_values, new_values)
|
||||||
elif command == "PUSH" or command == "POP":
|
elif command == "PUSH" or command == "POP":
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@ -860,15 +864,14 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
|
|
||||||
c.close()
|
c.close()
|
||||||
|
|
||||||
def __undo_redo_update_update(self, c, undo, command, table, column, pk, old_values, new_values):
|
def __undo_redo_update_update(self, c, undo, command, table, columns, pk, old_values, new_values):
|
||||||
if table is None:
|
if table is None or command != "UPDATE":
|
||||||
return
|
return
|
||||||
|
|
||||||
|
for column in columns:
|
||||||
# Update tree model and emit signals
|
# Update tree model and emit signals
|
||||||
# We can not easily implement this using triggers because they are called
|
# We can not easily implement this using triggers because they are called
|
||||||
# even if the transaction is rollback because of a FK constraint
|
# even if the transaction is rollback because of a FK constraint
|
||||||
|
|
||||||
if command == "UPDATE":
|
|
||||||
if table == "object":
|
if table == "object":
|
||||||
obj = self.get_object_by_id(pk[0], pk[1])
|
obj = self.get_object_by_id(pk[0], pk[1])
|
||||||
if obj:
|
if obj:
|
||||||
@ -911,7 +914,7 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
if obj:
|
if obj:
|
||||||
obj.notify(column)
|
obj.notify(column)
|
||||||
|
|
||||||
def __undo_redo_update_insert_delete(self, c, undo, command, table, column, pk, old_values, new_values):
|
def __undo_redo_update_insert_delete(self, c, undo, command, table, columns, pk, old_values, new_values):
|
||||||
if table is None:
|
if table is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -990,9 +993,8 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
def __undo_redo(self, undo):
|
def __undo_redo(self, undo):
|
||||||
selection = self.get_selection()
|
selection = self.get_selection()
|
||||||
|
|
||||||
command, range_id, table, column = self.db.execute(
|
command, range_id, table = self.db.execute(
|
||||||
"SELECT command, range_id, table_name, column_name FROM history WHERE history_id=?",
|
"SELECT command, range_id, table_name FROM history WHERE history_id=?", (self.history_index,)
|
||||||
(self.history_index, )
|
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
update_parents = []
|
update_parents = []
|
||||||
@ -1206,16 +1208,18 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
def get_msg(index):
|
def get_msg(index):
|
||||||
cmd = c.execute(
|
cmd = c.execute(
|
||||||
"""
|
"""
|
||||||
SELECT command, range_id, table_name, column_name, message, old_values, new_values
|
SELECT command, range_id, table_name, columns, message, old_values, new_values
|
||||||
FROM history
|
FROM history
|
||||||
WHERE history_id=?
|
WHERE history_id=?
|
||||||
""",
|
""",
|
||||||
(index,)
|
(index,),
|
||||||
).fetchone()
|
).fetchone()
|
||||||
|
|
||||||
if cmd is None:
|
if cmd is None:
|
||||||
return None
|
return None
|
||||||
command, range_id, table, column, message, old_values, new_values = cmd
|
command, range_id, table, columns, message, old_values, new_values = cmd
|
||||||
|
|
||||||
|
columns = json.loads(columns) if columns else []
|
||||||
|
|
||||||
if message is not None:
|
if message is not None:
|
||||||
return message
|
return message
|
||||||
@ -1283,10 +1287,14 @@ class CmbProject(GObject.Object, Gio.ListModel):
|
|||||||
.get(command, None)
|
.get(command, None)
|
||||||
)
|
)
|
||||||
|
|
||||||
if msg is not None:
|
if msg is None:
|
||||||
msg = msg.format(**get_msg_vars(table, column, values))
|
return None
|
||||||
|
|
||||||
return msg
|
msgs = []
|
||||||
|
for column in columns:
|
||||||
|
msgs.append(msg.format(**get_msg_vars(table, column, values)))
|
||||||
|
|
||||||
|
return "\n".join(msgs) if len(msgs) > 1 else (msgs[0] if msgs else None)
|
||||||
|
|
||||||
undo_msg = get_msg(self.history_index)
|
undo_msg = get_msg(self.history_index)
|
||||||
redo_msg = get_msg(self.history_index + 1)
|
redo_msg = get_msg(self.history_index + 1)
|
||||||
|
@ -41,7 +41,7 @@ CREATE TABLE history (
|
|||||||
command TEXT NOT NULL,
|
command TEXT NOT NULL,
|
||||||
range_id INTEGER REFERENCES history,
|
range_id INTEGER REFERENCES history,
|
||||||
table_name TEXT,
|
table_name TEXT,
|
||||||
column_name TEXT,
|
columns JSON,
|
||||||
message TEXT,
|
message TEXT,
|
||||||
table_pk JSON,
|
table_pk JSON,
|
||||||
new_values JSON,
|
new_values JSON,
|
||||||
|
@ -14,6 +14,15 @@
|
|||||||
</p>
|
</p>
|
||||||
</description>
|
</description>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release date="2024-12-13" version="0.94.1">
|
||||||
|
<description>
|
||||||
|
<p>First bugfix release in the series!</p>
|
||||||
|
<ul>
|
||||||
|
<li>Fix issue with undoing a parent change.</li>
|
||||||
|
<li>Fix workspace initialization error (#253)</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release date="2024-11-27" version="0.94.0">
|
<release date="2024-11-27" version="0.94.0">
|
||||||
<description>
|
<description>
|
||||||
<p>Accessibility Release!</p>
|
<p>Accessibility Release!</p>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
project(
|
project(
|
||||||
'cambalache', 'c',
|
'cambalache', 'c',
|
||||||
version: '0.94.0',
|
version: '0.94.1',
|
||||||
meson_version: '>= 0.64.0',
|
meson_version: '>= 0.64.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user