import frappe from frappe import _ from frappe.database.schema import DBTable from frappe.utils.defaults import get_not_null_defaults class SQLiteTable(DBTable): def create(self): # First prepare the basic table creation without indexes additional_definitions = [] name_column = "name TEXT PRIMARY KEY" # columns column_defs = self.get_column_definitions() if column_defs: additional_definitions += column_defs index_defs = [] # Store index definitions separately # child table columns if self.meta.get("istable", default=0): additional_definitions.extend(["parent TEXT", "parentfield TEXT", "parenttype TEXT"]) index_defs.append(f"CREATE INDEX `{self.table_name}_parent_idx` ON `{self.table_name}`(parent)") else: # parent types index_defs.append( f"CREATE INDEX `{self.table_name}_creation_idx` ON `{self.table_name}`(creation)" ) if self.meta.sort_field == "modified": index_defs.append( f"CREATE INDEX `{self.table_name}_modified_idx` ON `{self.table_name}`(modified)" ) # creating sequence(s) if not self.meta.issingle and self.meta.autoname == "autoincrement": name_column = "name INTEGER PRIMARY KEY AUTOINCREMENT" elif not self.meta.issingle and self.meta.autoname == "UUID": name_column = "name TEXT PRIMARY KEY" additional_definitions = ",\n".join(additional_definitions) # create table create_table_query = f"""CREATE TABLE `{self.table_name}` ( {name_column}, creation DATETIME, modified DATETIME, modified_by TEXT, owner TEXT, docstatus INTEGER NOT NULL DEFAULT 0, idx INTEGER NOT NULL DEFAULT 0, {additional_definitions})""" # Execute table creation frappe.db.sql_ddl(create_table_query) # Create indexes separately for index_query in index_defs: frappe.db.sql_ddl(index_query) def alter(self): for col in self.columns.values(): col.build_for_alter_table(self.current_columns.get(col.fieldname.lower())) for col in self.add_column: frappe.db.sql_ddl( f"ALTER TABLE `{self.table_name}` ADD COLUMN `{col.fieldname}` {col.get_definition()}" ) if not ( self.change_type or self.set_default or self.change_nullability or self.add_index or self.add_unique or self.drop_index or self.drop_unique ): return # Get current table column definitions existing_columns = [] for column in frappe.db.sql(f"PRAGMA table_info(`{self.table_name}`)", as_dict=1): existing_columns.append(f"`{column.name}` {column.type}") columns = existing_columns.copy() # Modify existing columns columns_to_modify = set(self.change_type + self.set_default + self.change_nullability) for col in columns_to_modify: # Replace the old column definition with the new one for i, column in enumerate(columns): if column.startswith(f"`{col.fieldname}`"): columns[i] = f"`{col.fieldname}` {col.get_definition(for_modification=True)}" break # Create new table temp_table = f"{self.table_name}_new" create_table = f"CREATE TABLE `{temp_table}` (\n{','.join(columns)}\n)" frappe.db.sql_ddl(create_table) # Copy data existing_columns = [col.split()[0] for col in existing_columns] column_list = ", ".join(existing_columns) frappe.db.sql_ddl(f"INSERT INTO `{temp_table}` SELECT {column_list} FROM `{self.table_name}`") # Drop old table frappe.db.sql_ddl(f"DROP TABLE `{self.table_name}`") # Rename new table frappe.db.sql_ddl(f"ALTER TABLE `{temp_table}` RENAME TO `{self.table_name}`") # Recreate indexes index_queries = [] if self.add_unique: index_queries.extend( f"CREATE UNIQUE INDEX `{col.fieldname}` ON `{self.table_name}` (`{col.fieldname}`)" for col in self.add_unique ) if self.add_index: index_queries.extend( f"CREATE INDEX `{col.fieldname}_index` ON `{self.table_name}` (`{col.fieldname}`)" for col in self.add_index if not frappe.db.get_column_index(self.table_name, col.fieldname, unique=False) ) if self.meta.sort_field == "modified" and not frappe.db.get_column_index( self.table_name, "modified", unique=False ): index_queries.append(f"CREATE INDEX `modified` ON `{self.table_name}` (`modified`)") for query in index_queries: frappe.db.sql_ddl(query) def alter_primary_key(self) -> str | None: # If there are no values in table allow migrating to UUID from TEXT autoname = self.meta.autoname if autoname == "UUID" and frappe.db.get_column_type(self.doctype, "name") != "TEXT": if not frappe.db.get_value(self.doctype, {}, order_by=None): return "ALTER COLUMN name TEXT" else: frappe.throw( _("Primary key of doctype {0} can not be changed as there are existing values.").format( self.doctype ) ) # Reverting from UUID to TEXT if autoname != "UUID" and frappe.db.get_column_type(self.doctype, "name") == "TEXT": return "ALTER COLUMN name TEXT"