<commit_before>#if SWIFT_PACKAGE
    import CSQLite
#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
    import SQLite3
#endif

extension Database {

    // MARK: - Database Schema

    /// Clears the database schema cache.
    ///
    /// You may need to clear the cache manually if the database schema is
    /// modified by another connection.
    public func clearSchemaCache() {
        SchedulingWatchdog.preconditionValidQueue(self)
        schemaCache.clear()
        
        // We also clear updateStatementCache and selectStatementCache despite
        // the automatic statement recompilation (see https://www.sqlite.org/c3ref/prepare.html)
        // because the automatic statement recompilation only happens a
        // limited number of times.
        internalStatementCache.clear()
        publicStatementCache.clear()
    }
    
    /// Returns whether a table exists.
    public func tableExists(_ name: String) throws -> Bool {
        return try exists(type: "table", name: name)
    }
    
    /// Returns whether a table is an internal SQLite table.
    ///
    /// Those are tables whose name begins with "sqlite_".
    ///
    /// For more information, see https://www.sqlite.org/fileformat2.html
    public func isSQLiteInternalTable(_ tableName: String) -> Bool {
        // https://www.sqlite.org/fileformat2.html#internal_schema_objects
        // > The names of internal schema objects always begin with "sqlite_"
        // > and any table, index, view, or trigger whose name begins with
        // > "sqlite_" is an internal schema object. SQLite prohibits
        // > applications from creating objects whose names begin with
        // > "sqlite_".
        return tableName.starts(with: "sqlite_")
    }
    
    /// Returns whether a table is an internal GRDB table.
    ///
    /// Those are tables whose name begins with "grdb_".
    public func isGRDBInternalTable(_ tableName: String) -> Bool {
        return tableName.starts(with: "grdb_")
    }
    
    /// Returns whether a view exists.
    public func viewExists(_ name: String) throws -> Bool {
        return try exists(type: "view", name: name)
    }
    
    /// Returns whether a trigger exists.
    public func triggerExists(_ name: String) throws -> Bool {
        return try exists(type: "trigger", name: name)
    }
    
    private func exists(type: String, name: String) throws -> Bool {
        // SQlite identifiers are case-insensitive, case-preserving:
        // http://www.alberton.info/dbms_identifiers_and_case_sensitivity.html
        let name = name.lowercased()
        
        if try makeSelectStatement("SELECT 1 FROM sqlite_master WHERE type = ? AND LOWER(name) = ?")
            .makeCursor(arguments: [type, name])
            .isEmpty() == false
        { return true }
        
        if try makeSelectStatement("SELECT 1 FROM sqlite_temp_master WHERE type = ? AND LOWER(name) = ?")
            .makeCursor(arguments: [type, name])
            .isEmpty() == false
        { return true }
        
        return false
    }

    /// The primary key for table named `tableName`.
    ///
    /// All tables have a primary key, even when it is not explicit. When a
    /// table has no explicit primary key, the result is the hidden
    /// "rowid" column.
    ///
    /// - throws: A DatabaseError if table does not exist.
    public func primaryKey(_ tableName: String) throws -> PrimaryKeyInfo {
        SchedulingWatchdog.preconditionValidQueue(self)
        
        if let primaryKey = schemaCache.primaryKey(tableName) {
            return primaryKey
        }
        
        // https://www.sqlite.org/pragma.html
        //
        // > PRAGMA database.table_info(table-name);
        // >
        // > This pragma returns one row for each column in the named table.
        // > Columns in the result set include the column name, data type,
        // > whether or not the column can be NULL, and the default value for
        // > the column. The "pk" column in the result set is zero for columns
        // > that are not part of the primary key, and is the index of the
        // > column in the primary key for columns that are part of the primary
        // > key.
        //
        // CREATE TABLE players (
        //   id INTEGER PRIMARY KEY,
        //   name TEXT,
        //   score INTEGER)
        //
        // PRAGMA table_info("players")
        //
        // cid | name  | type    | notnull | dflt_value | pk |
        // 0   | id    | INTEGER | 0       | NULL       | 1  |
        // 1   | name  | TEXT    | 0       | NULL       | 0  |
        // 2   | score | INTEGER | 0       | NULL       | 0  |
        
        let columns = try self.columns(in: tableName)
        
        let primaryKey: PrimaryKeyInfo
        let pkColumns = columns
            .filter { $0.primaryKeyIndex > 0 }
            .sorted { $0.primaryKeyIndex < $1.primaryKeyIndex }
        
        switch pkColumns.count {
        case 0:
            // No explicit primary key => primary key is hidden rowID column
            primaryKey = .hiddenRowID
        case 1:
            // Single column
            let pkColumn = pkColumns.first!
            
            // https://www.sqlite.org/lang_createtable.html:
            //
            // > With one exception noted below, if a rowid table has a primary
            // > key that consists of a single column and the declared type of
            // > that column is "INTEGER" in any mixture of upper and lower
            // > case, then the column becomes an alias for the rowid. Such a
            // > column is usually referred to as an "integer primary key".
            // > A PRIMARY KEY column only becomes an integer primary key if the
            // > declared type name is exactly "INTEGER". Other integer type
            // > names like "INT" or "BIGINT" or "SHORT INTEGER" or "UNSIGNED
            // > INTEGER" causes the primary key column to behave as an ordinary
            // > table column with integer affinity and a unique index, not as
            // > an alias for the rowid.
            // >
            // > The exception mentioned above is that if the declaration of a
            // > column with declared type "INTEGER" includes an "PRIMARY KEY
            // > DESC" clause, it does not become an alias for the rowid [...]
            //
            // FIXME: We ignore the exception, and consider all INTEGER primary
            // keys as aliases for the rowid:
            if pkColumn.type.uppercased() == "INTEGER" {
                primaryKey = .rowID(pkColumn.name)
            } else {
                primaryKey = .regular([pkColumn.name])
            }
        default:
            // Multi-columns primary key
            primaryKey = .regular(pkColumns.map { $0.name })
        }
        
        schemaCache.set(primaryKey: primaryKey, forTable: tableName)
        return primaryKey
    }
    
    /// The indexes on table named `tableName`; returns the empty array if the
    /// table does not exist.
    ///
    /// Note: SQLite does not define any index for INTEGER PRIMARY KEY columns:
    /// this method does not return any index that represents this primary key.
    ///
    /// If you want to know if a set of columns uniquely identify a row, prefer
    /// table(_:hasUniqueKey:) instead.
    public func indexes(on tableName: String) throws -> [IndexInfo] {
        if let indexes = schemaCache.indexes(on: tableName) {
            return indexes
        }
        
        let indexes = try Row.fetchAll(self, "PRAGMA index_list(\(tableName.quotedDatabaseIdentifier))").map { row -> IndexInfo in
            let indexName: String = row[1]
            let unique: Bool = row[2]
            let columns = try Row.fetchAll(self, "PRAGMA index_info(\(indexName.quotedDatabaseIdentifier))")
                .map { ($0[0] as Int, $0[2] as String) }
                .sorted { $0.0 < $1.0 }
                .map { $0.1 }
            return IndexInfo(name: indexName, columns: columns, unique: unique)<commit_msg>Cleanup<commit_after>#if SWIFT_PACKAGE
    import CSQLite
#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
    import SQLite3
#endif

extension Database {

    // MARK: - Database Schema

    /// Clears the database schema cache.
    ///
    /// You may need to clear the cache manually if the database schema is
    /// modified by another connection.
    public func clearSchemaCache() {
        SchedulingWatchdog.preconditionValidQueue(self)
        schemaCache.clear()
        
        // We also clear updateStatementCache and selectStatementCache despite
        // the automatic statement recompilation (see https://www.sqlite.org/c3ref/prepare.html)
        // because the automatic statement recompilation only happens a
        // limited number of times.
        internalStatementCache.clear()
        publicStatementCache.clear()
    }
    
    /// Returns whether a table exists.
    public func tableExists(_ name: String) throws -> Bool {
        return try exists(type: "table", name: name)
    }
    
    /// Returns whether a table is an internal SQLite table.
    ///
    /// Those are tables whose name begins with "sqlite_".
    ///
    /// For more information, see https://www.sqlite.org/fileformat2.html
    public func isSQLiteInternalTable(_ tableName: String) -> Bool {
        // https://www.sqlite.org/fileformat2.html#internal_schema_objects
        // > The names of internal schema objects always begin with "sqlite_"
        // > and any table, index, view, or trigger whose name begins with
        // > "sqlite_" is an internal schema object. SQLite prohibits
        // > applications from creating objects whose names begin with
        // > "sqlite_".
        return tableName.starts(with: "sqlite_")
    }
    
    /// Returns whether a table is an internal GRDB table.
    ///
    /// Those are tables whose name begins with "grdb_".
    public func isGRDBInternalTable(_ tableName: String) -> Bool {
        return tableName.starts(with: "grdb_")
    }
    
    /// Returns whether a view exists.
    public func viewExists(_ name: String) throws -> Bool {
        return try exists(type: "view", name: name)
    }
    
    /// Returns whether a trigger exists.
    public func triggerExists(_ name: String) throws -> Bool {
        return try exists(type: "trigger", name: name)
    }
    
    private func exists(type: String, name: String) throws -> Bool {
        // SQlite identifiers are case-insensitive, case-preserving:
        // http://www.alberton.info/dbms_identifiers_and_case_sensitivity.html
        let name = name.lowercased()
        
        if try makeSelectStatement("SELECT 1 FROM sqlite_master WHERE type = ? AND LOWER(name) = ?")
            .makeCursor(arguments: [type, name])
            .isEmpty() == false
        { return true }
        
        if try makeSelectStatement("SELECT 1 FROM sqlite_temp_master WHERE type = ? AND LOWER(name) = ?")
            .makeCursor(arguments: [type, name])
            .isEmpty() == false
        { return true }
        
        return false
    }

    /// The primary key for table named `tableName`.
    ///
    /// All tables have a primary key, even when it is not explicit. When a
    /// table has no explicit primary key, the result is the hidden
    /// "rowid" column.
    ///
    /// - throws: A DatabaseError if table does not exist.
    public func primaryKey(_ tableName: String) throws -> PrimaryKeyInfo {
        SchedulingWatchdog.preconditionValidQueue(self)
        
        if let primaryKey = schemaCache.primaryKey(tableName) {
            return primaryKey
        }
        
        // https://www.sqlite.org/pragma.html
        //
        // > PRAGMA database.table_info(table-name);
        // >
        // > This pragma returns one row for each column in the named table.
        // > Columns in the result set include the column name, data type,
        // > whether or not the column can be NULL, and the default value for
        // > the column. The "pk" column in the result set is zero for columns
        // > that are not part of the primary key, and is the index of the
        // > column in the primary key for columns that are part of the primary
        // > key.
        //
        // CREATE TABLE players (
        //   id INTEGER PRIMARY KEY,
        //   name TEXT,
        //   score INTEGER)
        //
        // PRAGMA table_info("players")
        //
        // cid | name  | type    | notnull | dflt_value | pk |
        // 0   | id    | INTEGER | 0       | NULL       | 1  |
        // 1   | name  | TEXT    | 0       | NULL       | 0  |
        // 2   | score | INTEGER | 0       | NULL       | 0  |
        
        let columns = try self.columns(in: tableName)
        
        let primaryKey: PrimaryKeyInfo
        let pkColumns = columns
            .filter { $0.primaryKeyIndex > 0 }
            .sorted { $0.primaryKeyIndex < $1.primaryKeyIndex }
        
        switch pkColumns.count {
        case 0:
            // No explicit primary key => primary key is hidden rowID column
            primaryKey = .hiddenRowID
        case 1:
            // Single column
            let pkColumn = pkColumns.first!
            
            // https://www.sqlite.org/lang_createtable.html:
            //
            // > With one exception noted below, if a rowid table has a primary
            // > key that consists of a single column and the declared type of
            // > that column is "INTEGER" in any mixture of upper and lower
            // > case, then the column becomes an alias for the rowid. Such a
            // > column is usually referred to as an "integer primary key".
            // > A PRIMARY KEY column only becomes an integer primary key if the
            // > declared type name is exactly "INTEGER". Other integer type
            // > names like "INT" or "BIGINT" or "SHORT INTEGER" or "UNSIGNED
            // > INTEGER" causes the primary key column to behave as an ordinary
            // > table column with integer affinity and a unique index, not as
            // > an alias for the rowid.
            // >
            // > The exception mentioned above is that if the declaration of a
            // > column with declared type "INTEGER" includes an "PRIMARY KEY
            // > DESC" clause, it does not become an alias for the rowid [...]
            //
            // FIXME: We ignore the exception, and consider all INTEGER primary
            // keys as aliases for the rowid:
            if pkColumn.type.uppercased() == "INTEGER" {
                primaryKey = .rowID(pkColumn.name)
            } else {
                primaryKey = .regular([pkColumn.name])
            }
        default:
            // Multi-columns primary key
            primaryKey = .regular(pkColumns.map { $0.name })
        }
        
        schemaCache.set(primaryKey: primaryKey, forTable: tableName)
        return primaryKey
    }
    
    /// The indexes on table named `tableName`; returns the empty array if the
    /// table does not exist.
    ///
    /// Note: SQLite does not define any index for INTEGER PRIMARY KEY columns:
    /// this method does not return any index that represents this primary key.
    ///
    /// If you want to know if a set of columns uniquely identify a row, prefer
    /// table(_:hasUniqueKey:) instead.
    public func indexes(on tableName: String) throws -> [IndexInfo] {
        if let indexes = schemaCache.indexes(on: tableName) {
            return indexes
        }
        
        let indexes = try Row
            .fetchAll(self, "PRAGMA index_list(\(tableName.quotedDatabaseIdentifier))")
            .map { row -> IndexInfo in
                // [seq:0 name:"index" unique:0 origin:"c" partial:0]
                let indexName: String = row[1]
                let unique: Bool = row[2]
                let columns = try Row
                    .fetchAll(self, "PRAGMA index_info(\(indexName.quotedDatabaseIdentifier))")
                    .map { row -> (Int, String) in
                        // [seqno:0 cid:2 name:"column"]
                        (row[0] as Int, row[2] as String)
                    }
                    .sorted { $0.0 < $1.0 }
                    .map { $0.1 }
                return IndexInfo(name: indexName, columns: columns, unique: unique)
{'fraction_non_alphanumeric': 0.09358550618190721, 'fraction_numerical': 0.004345697147753703, 'mean_word_length': 1.7693220338983051, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 84, 'https://': 10, 'lorem ipsum': 0, 'www.': 12, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2245047', 'n_tokens_mistral': 4395, 'n_tokens_neox': 4238, 'n_words': 1578}