---
name: oro-dba
description: OroCommerce database administration reference covering PostgreSQL configuration, Doctrine ORM mapping, migrations, entity extensions, schema management, indexing, performance optimization, and data management. Use when working with database schema, writing migrations, optimizing queries, configuring PostgreSQL, or troubleshooting database issues in OroCommerce.
---

# OroCommerce DBA

Database administration and schema reference for OroCommerce.
Docs: https://doc.oroinc.com/backend/entities/

## Database Requirements

- PostgreSQL >= 16.1 (only supported RDBMS)
- `uuid-ossp` extension required: `CREATE EXTENSION "uuid-ossp";`
- Doctrine ORM with Oro Doctrine Extensions for PG-specific functions

## Doctrine ORM in Oro

Oro uses standard Doctrine ORM with PHP 8 attributes:

```php
#[ORM\Entity]
#[ORM\Table(name: 'acme_example')]
#[ORM\Index(columns: ['status'], name: 'idx_acme_example_status')]
#[ORM\UniqueConstraint(columns: ['external_id'], name: 'uniq_acme_ext_id')]
class Example { }
```

Oro-specific: `#[Config]` attribute adds entity config metadata (ACL, ownership,
audit, extend support). This is processed by `EntityConfigBundle`.

## Migration System

Oro uses its own migration system (not Doctrine Migrations).

### Migration Interface

```php
use Oro\Bundle\MigrationBundle\Migration\Migration;

class AddStatusColumn implements Migration
{
    public function up(Schema $schema, QueryBag $queries): void
    {
        $table = $schema->getTable('acme_example');
        $table->addColumn('status', 'string', ['length' => 32, 'default' => 'new']);
        $table->addIndex(['status'], 'idx_acme_example_status');
    }
}
```

### Installation Interface (Initial Schema)

```php
use Oro\Bundle\MigrationBundle\Migration\Installation;

class AcmeExampleInstaller implements Installation
{
    public function getMigrationVersion(): string { return 'v1_0'; }

    public function up(Schema $schema, QueryBag $queries): void
    {
        $table = $schema->createTable('acme_example');
        $table->addColumn('id', 'integer', ['autoincrement' => true]);
        // ... all columns
        $table->setPrimaryKey(['id']);
    }
}
```

### Data Migrations

Use `QueryBag` for INSERT/UPDATE/DELETE:

```php
public function up(Schema $schema, QueryBag $queries): void
{
    $queries->addQuery("INSERT INTO oro_enum_value ...");
    $queries->addPreQuery("UPDATE ..."); // runs before schema changes
    $queries->addPostQuery("UPDATE ..."); // runs after schema changes
}
```

### Migration Commands

```bash
php bin/console oro:migration:load              # run pending migrations
php bin/console oro:migration:dump              # generate diff migration
php bin/console oro:entity-extend:update-schema # update extended entity schema
php bin/console oro:entity-extend:update-schema --dry-run
```

### Migration File Location

```
Migrations/Schema/
    v1_0/CreateExampleTable.php
    v1_1/AddStatusColumn.php
    v1_2/AddForeignKeys.php
```

Version directories must sort lexicographically (v1_0 < v1_1 < v1_10).

Multiple migration files per version directory are allowed and common:

```
Migrations/Schema/
    v1_13/
        AddNewFields.php
        RemoveObsoleteFields.php
        UpdateFieldLabels.php
```

### Extending Oro Base Entities

Custom entities can extend Oro base entity classes (e.g., `Transport` for shipping/payment integrations):

```php
#[ORM\Entity]
class FreightShippingSettings extends Transport
{
    #[ORM\ManyToMany(targetEntity: LocalizedFallbackValue::class, cascade: ['ALL'], orphanRemoval: true)]
    #[ORM\JoinTable(name: 'acme_freight_transport_label')]
    #[ORM\JoinColumn(name: 'transport_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
    #[ORM\InverseJoinColumn(name: 'localized_value_id', referencedColumnName: 'id', onDelete: 'CASCADE', unique: true)]
    private Collection $labels;
}
```

### Doctrine Mapping Configuration

For app-level entities outside bundles, configure in `config/doctrine.yml`:

```yaml
doctrine:
    orm:
        mappings:
            App:
                is_bundle: false
                type: attribute
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App
```

Bundle entities are auto-discovered -- no explicit mapping needed.

## Entity Extension Schema

Extended fields use `oro_options` in column definitions:

```php
$table->addColumn('custom_field', 'string', [
    'oro_options' => [
        'extend' => [
            'is_extend' => true,
            'owner' => ExtendScope::OWNER_CUSTOM,  // or OWNER_SYSTEM
            'nullable' => true,
        ],
        'entity' => ['label' => 'Custom Field'],
        'datagrid' => ['is_visible' => DatagridScope::IS_VISIBLE_TRUE],
    ]
]);
```

### Adding Enum Fields via ExtendExtension

For select/multiselect fields on Oro core entities, use `addEnumField`:

```php
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtension;
use Oro\Bundle\EntityExtendBundle\Migration\Extension\ExtendExtensionAwareInterface;

class AddFrequencyEnum implements Migration, ExtendExtensionAwareInterface
{
    protected ExtendExtension $extendExtension;

    public function setExtendExtension(ExtendExtension $extendExtension): void
    {
        $this->extendExtension = $extendExtension;
    }

    public function up(Schema $schema, QueryBag $queries): void
    {
        $this->extendExtension->addEnumField(
            $schema,
            $schema->getTable('oro_product'),
            'frequency',                       // field name
            'acme_product_frequency',        // enum code (must be unique)
            false,                             // is_multiple (false = select, true = multiselect)
            false,                             // is_public
            [
                'extend' => ['owner' => ExtendScope::OWNER_CUSTOM],
                'entity' => ['label' => 'Frequency']
            ]
        );
    }
}
```

Populate enum values via data migration:

```php
use Oro\Bundle\EntityExtendBundle\Migration\Fixture\AbstractEnumFixture;

class LoadFrequencyEnumValues extends AbstractEnumFixture
{
    protected function getData(): array
    {
        return [
            '50hz' => false,  // value => is_default
            '60hz' => true,
            'variable' => false,
        ];
    }

    protected function getEnumCode(): string
    {
        return 'acme_product_frequency';
    }
}
```

### Datagrid Visibility in oro_options

| Value | Effect |
|---|---|
| `DatagridScope::IS_VISIBLE_TRUE` | Shown by default |
| `DatagridScope::IS_VISIBLE_HIDDEN` | Available but hidden by default |
| `DatagridScope::IS_VISIBLE_FALSE` | Not available in datagrid |

Extended entity schema is managed separately:
- Proxy classes generated in cache
- `oro:entity-extend:update-schema` applies extend changes
- `oro:entity-extend:cache:clear` rebuilds proxies

## Table Naming Conventions

| Scope | Pattern | Example |
|---|---|---|
| Custom entities | `acme_{bundle}_{entity}` | `acme_cpn_customer_part_number` |
| Oro core | `oro_{entity}` | `oro_order` |
| Extend fields | auto-generated columns on existing table | `custom_field` on `oro_order` |
| Enum tables | `oro_enum_{code}` | `oro_enum_order_status` |

## Indexing Guidance

### When to Add Indexes
- Columns used in WHERE clauses
- Foreign key columns
- Columns used in ORDER BY
- Columns used in datagrid filters/sorters

### Oro Datagrid Query Patterns
Datagrids generate ORM queries. The `data_name` in sorters and filters
maps directly to query aliases. Ensure underlying columns are indexed.

### Composite Indexes
```php
$table->addIndex(['organization_id', 'status'], 'idx_org_status');
```

## Key Oro Tables

| Table | Purpose |
|---|---|
| `oro_user` | Admin/back-office users |
| `oro_customer` | Commerce customers (organizations) |
| `oro_customer_user` | Commerce storefront users |
| `oro_order` | Orders |
| `oro_product` | Products |
| `oro_category` | Product categories |
| `oro_shopping_list` | Shopping lists |
| `oro_checkout` | Checkout sessions |
| `oro_price_list` | Price lists |
| `oro_workflow_item` | Workflow state |
| `oro_message_queue` | DBAL message queue |
| `oro_entity_config` | Entity configuration metadata |
| `oro_migration` | Migration version tracking |

## Performance Considerations

### PostgreSQL Tuning
- `shared_buffers`: 25% of RAM
- `effective_cache_size`: 75% of RAM
- `work_mem`: 4-16MB (depends on concurrent queries)
- `maintenance_work_mem`: 256MB-1GB for migrations/vacuum
- `random_page_cost`: 1.1 for SSD
- Enable `pg_stat_statements` for query monitoring

### Oro-Specific Performance
- Extend entity cache warmup is CPU-intensive; run during deployment
- Migration load can be slow on large schemas; use maintenance windows
- DBAL MQ polls every 1s by default; tune `polling_interval` or use RabbitMQ
- Elasticsearch offloads search from PostgreSQL (EE)
- Redis caching reduces DB query load

### Doctrine Query Hints
Oro uses Doctrine query hints for:
- `HINT_PRECISE_ORDER_BY` -- deterministic ordering
- Custom walker hints for ACL filtering
- Translation hints for localized queries

## Backup and Recovery

Standard PostgreSQL tools apply:
```bash
pg_dump -Fc dbname > backup.dump          # custom format backup
pg_restore -d dbname backup.dump          # restore
pg_dump --schema-only dbname > schema.sql # schema only
```

Always back up before `oro:migration:load` or `oro:entity-extend:update-schema`.

## Data Fixtures

Seed data in `Migrations/Data/ORM/`, demo data in `Migrations/Data/Demo/ORM/`.
Fixtures implement `FixtureInterface`. Use `OrderedFixtureInterface` for ordering.
Load: `php bin/console oro:migration:data:load` (`--fixtures-type=demo` for demo).
Preview: `--dry-run`. Scope: `--bundles=BundleName`.

## Data Audit Tables

When `dataaudit` is enabled, changes tracked in `oro_audit` and `oro_audit_field`.
Each entry stores: entity class, entity ID, action, user, timestamp, version.
Segment filters can query audit data for historical analysis.
