SuperAPI AI Directory

nextcloud-errors-database

Data & analytics

View repository

Updated 10-15 22:18

About this skill

nextcloud-errors-database

Quick Diagnostic Reference

Error Category Index

Symptom Category Jump To
Migration runs but nothing changes Migration E-01
Table already exists exception Migration E-02
Data migration reads NULL for new column Migration E-03
Migration name does not match warning Migration E-04
SQL syntax error on Oracle/PostgreSQL Query E-05
SQL injection vulnerability detected Query E-06
Database connection exhausted / timeouts Query E-07
LIKE query returns unexpected results Query E-08
Entity property returns string instead of int Entity E-09
Column not found for entity property Entity E-10
Table not found: oc_oc_myapp_items Entity E-11
ORA-00972: identifier is too long Oracle E-12
ORA-01400: cannot insert NULL on boolean Oracle E-13
ORA-01400: cannot insert NULL on string Oracle E-14
ORA-00972 on column/index/FK name Oracle E-15
ORA-01461: can bind LONG value only Oracle E-16
Replication fails silently Galera E-17
Duplicate index name across apps Index E-18
Partial insert committed on error Transaction E-19
Lock timeout / deadlock in transaction Transaction E-20

Migration Errors

E-01: Modified Existing Migration

Symptom: Migration runs without errors but schema changes do not appear. New installations work correctly but upgrades do not.

Cause: An already-executed migration file was edited. Nextcloud stores executed migration class names in oc_migrations and NEVER re-runs them.

Fix: ALWAYS create a new migration class for any schema change after the original migration has been committed.

// WRONG: Editing Version1000Date20240101000000 after deployment
// CORRECT: Create Version1001Date20240215000000 with the change
class Version1001Date20240215000000 extends SimpleMigrationStep {
    public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
        $schema = $schemaClosure();
        $table = $schema->getTable('myapp_items');
        if (!$table->hasColumn('priority')) {
            $table->addColumn('priority', Types::INTEGER, ['notnull' => true, 'default' => 0]);
        }
        return $schema;
    }
}

E-02: Missing Existence Check

Symptom: Doctrine\DBAL\Exception\TableExistsException or column already exists during migration.

Cause: Migration calls createTable() or addColumn() without checking if the table or column already exists.

Fix: ALWAYS wrap creation calls with hasTable() / hasColumn() guards.

if (!$schema->hasTable('myapp_items')) {
    $table = $schema->createTable('myapp_items');
    // ...
}
// For columns:
$table = $schema->getTable('myapp_items');
if (!$table->hasColumn('new_col')) {
    $table->addColumn('new_col', Types::STRING, ['notnull' => false, 'length' => 255]);
}

E-03: Data Migration in changeSchema()

Symptom: Query in changeSchema() fails because the new column does not exist yet, or data reads return NULL for newly added columns.

Cause: changeSchema() defines the schema diff but the actual SQL has NOT been executed yet. Data queries against new columns fail.

Fix: ALWAYS use postSchemaChange() for data operations.

public function changeSchema(...): ?ISchemaWrapper {
    // Schema changes ONLY — no data queries
    return $schema;
}

public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
    // Data operations AFTER schema is applied
    $qb = $this->db->getQueryBuilder();
    $qb->update('myapp_items')->set('new_col', 'old_col')->executeStatement();
}

E-04: Wrong Migration Naming

Symptom: Nextcloud logs warnings about migration naming or migrations run in unexpected order.

Cause: Migration class name does not follow Version{MajorMinor}Date{YYYYMMDDHHmmss} convention.

Fix: ALWAYS use the correct naming pattern. Version mapping: 1.0.x => Version1000, 2.4.x => Version2004, 24.0.x => Version24000.


Query Builder Errors

E-05: Raw SQL Instead of Query Builder

Symptom: Query works on MySQL but fails on PostgreSQL, SQLite, or Oracle with syntax errors.

Cause: Raw SQL uses MySQL-specific syntax (backtick quoting, LIMIT syntax, IFNULL).

Fix: NEVER use raw SQL. ALWAYS use the query builder for cross-database portability.

// WRONG
$this->db->executeQuery("SELECT * FROM oc_myapp_items WHERE user_id = '$userId'");

// CORRECT
$qb = $this->db->getQueryBuilder();
$qb->select('*')
    ->from('myapp_items')
    ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
$result = $qb->executeQuery();

E-06: String Concatenation in Queries

Symptom: SQL injection vulnerability. Unexpected query results or database corruption.

Cause: User input concatenated directly into query strings instead of using named parameters.

Fix: ALWAYS use $qb->createNamedParameter() for ALL query values.

// WRONG — SQL injection
$qb->where("user_id = '$userId'");
$qb->where($qb->expr()->eq('user_id', "'$userId'"));

// CORRECT
$qb->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));

E-07: Unclosed Result Cursors

Symptom: Database connection pool exhaustion. "Too many connections" errors. Memory leaks during long-running operations.

Cause: closeCursor() not called after processing query results. Open cursors hold database connections.

Fix: ALWAYS call $result->closeCursor() after processing. Note: QBMapper::findEntity() and findEntities() close cursors automatically.

$result = $qb->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor(); // NEVER forget this
return $rows;

E-08: Unescaped LIKE Parameters

Symptom: LIKE query returns unexpected results when user input contains % or _ characters.

Cause: SQL wildcards in user input are not escaped before use in LIKE expressions.

Fix: ALWAYS use $this->db->escapeLikeParameter() for user input in LIKE queries.

// WRONG
$qb->andWhere($qb->expr()->like('name', $qb->createNamedParameter('%' . $userInput . '%')));

// CORRECT
$qb->andWhere($qb->expr()->iLike('name',
    $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($userInput) . '%')));

Entity Mapping Errors

E-09: Missing addType() in Entity

Symptom: Entity property typed as ?int returns string "5" instead of integer 5. Boolean property returns "1" instead of true.

Cause: The addType() call is missing in the entity constructor. Without it, all database values are returned as strings.

Fix: ALWAYS call addType() for every non-string property in the entity constructor.

class Item extends Entity {
    protected ?int $count = null;
    protected ?bool $active = null;
    protected ?\DateTime $createdAt = null;

    public function __construct() {
        $this->addType('count', 'integer');
        $this->addType('active', 'boolean');
        $this->addType('createdAt', 'datetime');
    }
}

E-10: CamelCase/snake_case Mismatch

Symptom: Column not found error when loading entities. Entity properties are NULL despite data existing in the database.

Cause: Entity property name does not match the expected column mapping. Nextcloud auto-converts camelCase properties to snake_case columns (phoneNumber maps to phone_number).

Fix: ALWAYS verify that entity property names in camelCase match the database column names in snake_case. Override columnToProperty() / propertyToColumn() only for non-standard mappings.

E-11: oc_ Prefix Included in Table Name

Symptom: Table not found: oc_oc_myapp_items. The prefix is doubled.

Cause: The oc_ prefix was manually included when passing the table name to QBMapper or query builder. The prefix is added automatically.

Fix: NEVER include the oc_ prefix in table names passed to QBMapper constructors or query builder from() calls.

// WRONG
parent::__construct($db, 'oc_myapp_items', Item::class);

// CORRECT
parent::__construct($db, 'myapp_items', Item::class);

Oracle Constraint Errors

E-12: Table Name Exceeds 23 Characters

Symptom: ORA-00972: identifier is too long during migration on Oracle.

Cause: Table name exceeds 23 characters. With the oc_ prefix (4 chars), the total exceeds Oracle's 30-character identifier limit.

Fix: ALWAYS keep table names at 23 characters or fewer.

// WRONG: 'myapp_project_collaborators' = 28 chars → oc_ prefix = 32
// CORRECT: 'myapp_proj_collabs' = 18 chars → oc_ prefix = 22

E-13: NOT NULL Boolean Column

Symptom: ORA-01400: cannot insert NULL when inserting rows with boolean columns on Oracle.

Cause: Boolean column declared as NOT NULL. Oracle does not support NOT NULL constraints on boolean columns.

Fix: NEVER use 'notnull' => true on boolean columns.

// WRONG
$table->addColumn('is_active', Types::BOOLEAN, ['notnull' => true, 'default' => false]);

// CORRECT
$table->addColumn('is_active', Types::BOOLEAN, ['notnull' => false, 'default' => false]);

E-14: NOT NULL String with Empty Default

Symptom: ORA-01400: cannot insert NULL when inserting rows with empty string values on Oracle.

Cause: String column declared as NOT NULL with 'default' => ''. Oracle treats empty strings as NULL, causing a constraint violation.

Fix: NEVER combine NOT NULL with an empty string default on string columns.

// WRONG
$table->addColumn('label', Types::STRING, ['notnull' => true, 'default' => '', 'length' => 255]);

// CORRECT
$table->addColumn('label', Types::STRING, ['notnull' => false, 'default' => null, 'length' => 255]);

E-15: Identifier Exceeds 30 Characters

Symptom: ORA-00972: identifier is too long for column names, index names, or foreign key names.

Cause: Identifier exceeds Oracle's 30-character limit.

Fix: ALWAYS keep column, index, and foreign key names at 30 characters or fewer.

E-16: String Exceeds 4000 Characters

Symptom: ORA-01461: can bind a LONG value only for insert when storing long strings.

Cause: String column value exceeds Oracle's 4,000-character VARCHAR2 limit.

Fix: Use Types::TEXT (CLOB) instead of Types::STRING for columns that may contain more than 4,000 characters.


Galera Cluster Errors

E-17: Table Without Primary Key

Symptom: Data written on one cluster node does not appear on other nodes. Replication fails silently.

Cause: Table created without a primary key. Galera Cluster uses row-based replication and requires primary keys on ALL tables.

Fix: ALWAYS define a primary key on every table. ALWAYS include an auto-incremented id BIGINT column.

$table = $schema->createTable('myapp_logs');
$table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true]);
$table->addColumn('message', Types::TEXT, ['notnull' => false]);
$table->setPrimaryKey(['id']); // NEVER omit this

Index Errors

E-18: Non-Unique Index Names

Symptom: Index already exists error during migration. Another app uses the same index name.

Cause: Index name is too generic (e.g., user_id_idx). Index names must be unique across the entire database.

Fix: ALWAYS prefix index names with your app name: {appname}_{table}_{columns}_idx.

// WRONG
$table->addIndex(['user_id'], 'user_id_idx');

// CORRECT
$table->addIndex(['user_id'], 'myapp_items_uid_idx');

Transaction Errors

E-19: Missing Transaction Rollback

Symptom: Partial data committed after an error. Database in inconsistent state.

Cause: Manual beginTransaction() / commit() without try/catch and rollBack().

Fix: ALWAYS use the TTransactional trait instead of manual transaction management.

use OCP\DB\TTransactional;

class MyService {
    use TTransactional;

    public function createBoth(): void {
        $this->atomic(function () {
            $this->mapper->insert($entity1);
            $this->mapper->insert($entity2);
        }, $this->db);
    }
}

E-20: Slow Operations Inside Transaction

Symptom: Lock timeouts, deadlocks, or degraded performance under load.

Cause: Long-running operations (HTTP calls, file I/O, heavy computation) executed inside a database transaction, holding locks.

Fix: ALWAYS perform slow operations OUTSIDE the transaction. Only database reads/writes belong inside $this->atomic().


Oracle/Galera Constraint Quick Reference

Constraint Limit Error If Violated
Table name Max 23 chars ORA-00972
Column name Max 30 chars ORA-00972
Index name Max 30 chars ORA-00972
FK name Max 30 chars ORA-00972
String column length Max 4,000 chars ORA-01461
Boolean NOT NULL NOT allowed ORA-01400
String NOT NULL + empty default NOT allowed ORA-01400
Primary key REQUIRED on every table Galera silent replication failure

Reference Links

Official Sources