OriginChainDB
SQL clients & compatibility

Compatible with the MySQL wire protocol

This page records the earlier MySQL-compatible listener and its tested subset. It is retained to help readers understand existing integrations, not to offer a new connection method.

Use HTTPS for new application connections, or a separate read-only PostgreSQL database login for supported clients. MySQL retirement changes are not a claim that every existing production listener has been disabled. Confirm an existing listener’s state with the team.

Review a legacy MySQL connectionThis adapter is historical; use HTTPS for new writes or the supported PostgreSQL login for reads.
  1. Identify the existing integrationCheck whether it still uses the historical MySQL adapter.
  2. Confirm the actual listenerObtain its endpoint and access contract from the operator.
  3. Verify TLS and identityDo not infer a working secure listener from a capability flag.
  4. Test a supported readCompare results before choosing a replacement connection.

What this is, and what it is not

This adapter implements part of a network protocol. It does not run MySQL server software or provide full MySQL behavior. The SQL and catalog limits below still apply.

The historical adapter supports the version-10 handshake, ordinary text queries and a prepared-statement subset. A prepared statement sends query structure separately from bound values. Test unsupported constructs as well as successful requests.

Row-level security and column masking are applied inside the engine, below the wire, so a MySQL session sees exactly the rows and columns your policies allow - the same ones an HTTP call under that identity would see.

Connect

First find out whether your instance has a listener at all. Ask the capabilities endpoint:

curl -s -H "Authorization: Bearer $OC_TOKEN" \
  "https://$OC_HOST/v1/capabilities" | jq '.wire.mysql'

# Until a listener is configured for your instance:
#   { "available": false }
#
# "available" reports that CONFIGURATION IS COMPLETE. It is a precondition
# for starting a listener, not an observation of a bound socket - so when it
# turns true, still check the port and a real login before you rely on it.

Once it is enabled, connect the way you would connect to anything speaking this protocol:

# The port follows the convention MySQL clients expect. The host and port
# your instance is actually published on are shown in the console once the
# listener is enabled - use those, not this default.

mysql --host "$OC_MYSQL_HOST" --port 3306 \
      --user "$OC_DB_USER" --password \
      --ssl-mode=REQUIRED \
      --database "$OC_DB"

The authentication plugin depends on which credential model your listener runs: mysql_native_password for a shared credential, caching_sha2_password for per-user login. Both are what a stock client already negotiates, so you do not configure the plugin - you configure the model, and the plugin follows.

TLS, and why a certificate is not optional

The listener implements the standard TLS upgrade: it advertises TLS support in the handshake, a capable client answers by asking to upgrade before it authenticates, and the rest of the session runs inside TLS. That is exactly what --ssl-mode=REQUIRED does, so stock clients interoperate without special handling.

a shared-credential listener fails open

The historical shared-credential listener could start without a certificate and serve plaintext. Do not use it without verified TLS. Database permissions do not protect data while it crosses an unencrypted network.

The historical per-user listener requires TLS and refuses to start without a certificate. Still configure the client to verify the server certificate.

Credential models, and which one you will be on

There are two, and the choice is not a preference - it is decided by whether your tenant has any database authorization to enforce: a database RBAC grant or a row-level security policy. Either one is enough; a row-security policy is not the lighter case.

ModelWhat the client sendsYou are on it when
sharedOne listener-wide password. Every session arrives as the same identity.Your tenant has no database RBAC grants and no row-level security policies at all. A shared credential cannot express a per-user grant or evaluate a per-user row policy, so the engine refuses to combine the two rather than serve them loosely.
per-userEach database user authenticates with its own password, and its own grants apply for the session.Your tenant has any database RBAC grant, or any row-level security policy. This is the only model that can serve it. TLS is mandatory here, and the session is read-only.

a per-user listener is read-only

A per-user MySQL session refuses every write. Not some writes, and not writes your grants happen to disallow - the listener marks the session read-only when it admits an authenticated identity, before any grant is consulted, because there is no write-parity bridge on this adapter. INSERT, UPDATE, DELETE and DDL all come back refused, and no configuration turns this on.

A tenant with database grants or row policies must use the authorization-capable login model. In this historical adapter, that model remains read-only.

The write examples below belong to the historical shared-credential model. For new application writes, use the supported HTTPS API. The currently offered PostgreSQL database login is read-only.

If a tenant requires per-user authorization but has no database user with a password, neither historical model can accept a login. Resolve the identity requirements with your administrator.

A worked example: Sequelize over mysql2

The example records Sequelize 6.37 with mysql2 3.24 on the historical shared-credential listener. It demonstrates a declared single-table model. It does not establish current production support or automatic schema reflection.

Connect and define

import mysql2 from "mysql2";
import { Sequelize, DataTypes } from "sequelize";

// Verified against the shipping engine with Sequelize 6.37 over mysql2 3.24.
const sequelize = new Sequelize(process.env.OC_DB, process.env.OC_DB_USER, process.env.OC_DB_PASSWORD, {
  host: process.env.OC_MYSQL_HOST,
  port: 3306,
  dialect: "mysql",
  dialectModule: mysql2,
  // Ask for TLS explicitly. A listener with no certificate will happily
  // serve you in cleartext instead of refusing - see "TLS" above.
  dialectOptions: { ssl: { minVersion: "TLSv1.2", rejectUnauthorized: true } },
  logging: false,
});

// Describe the table in code. Do NOT call sequelize.sync() and do not rely on
// queryInterface.describeTable() - both introspect with SHOW, which is refused.
const Order = sequelize.define(
  "Order",
  {
    id: { type: DataTypes.STRING, primaryKey: true },
    customer: DataTypes.STRING,
    total_cents: DataTypes.INTEGER,
    status: DataTypes.STRING,
  },
  { tableName: "orders", timestamps: false, freezeTableName: true },
);

await sequelize.authenticate();

Insert, select, update, delete

// INSERT
await Order.create({ id: "o_1", customer: "c_7", total_cents: 4200, status: "open" });

// SELECT with a filter and an ordering
const open = await Order.findAll({
  where: { status: "open" },
  order: [["total_cents", "DESC"]],
  limit: 10,
});

// UPDATE
await Order.update({ status: "shipped" }, { where: { id: "o_1" } });

// DELETE, bounded by a WHERE
await Order.destroy({ where: { id: "o_1" } });

Transactions

Transactions are real, not acknowledged-and-ignored. A block that commits persists, and a block that rolls back leaves nothing behind - both were exercised.

// Both outcomes were exercised against the engine: a transaction that
// commits, and one that rolls back leaving no trace.
const tx = await sequelize.transaction();
try {
  await Order.create({ id: "o_2", customer: "c_7", total_cents: 900, status: "open" }, { transaction: tx });
  await Order.update({ status: "paid" }, { where: { id: "o_2" }, transaction: tx });
  await tx.commit();
} catch (err) {
  await tx.rollback();
  throw err;
}

More recipes, one per page, are under MySQL examples.

The catalog surface

Catalog queries let a tool inspect tables and fields. The historical adapter supports the information_schema views listed below, but refuses SHOW commands. A tool that depends on SHOW may fail even when ordinary reads work.

Answered: the information_schema views

Five views are served, by the same shared catalog the other wire protocols read: tables, columns, table_constraints, key_column_usage and referential_constraints. They are ordinary queries, so they work from any client:

-- Answered by the shared catalog, the same one the other wire
-- protocols and the HTTP API read.
SELECT table_name FROM information_schema.tables WHERE table_schema = 'shop';
SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'orders';

Refused: every SHOW form

mysql> SHOW TABLES;
ERROR 1235 (42000): MySQL `SHOW …` metadata commands are not supported —
the engine keeps no MySQL information schema.

-- Refusing is the deliberate choice. The shared front door carries a
-- PostgreSQL "SHOW <guc>" shim that would otherwise answer ANY SHOW with a
-- single fabricated row reading  x = 'on'  - so SHOW TABLES would return a
-- confident, wrong answer instead of an error.

SHOW TABLES, SHOW DATABASES, SHOW COLUMNS, SHOW CREATE TABLE, SHOW INDEX, SHOW WARNINGS and SHOW VARIABLES LIKE … all return an error. We would rather hand you an error you can act on than a fabricated row you would believe.

Session variables are handled separately and honestly: the ones a connector reads at startup are answered with their true values, and one that does not exist is refused with the unknown-variable error rather than a made-up value. SET NAMES is accepted as a no-op, because every official connector opens with one.

Limits

Every one of these returns a clear error naming the construct. None of them silently returns a different answer.

the one that decides adoption

Test both metadata queries and application queries. The historical adapter does not establish support for ORM associations or automatic schema management. For new work, use HTTPS or the currently offered read-only PostgreSQL login.

Not supportedWhy, and what to write instead
SHOW …No MySQL information schema is kept. Query the information_schema views listed above instead.
REPLACE INTOIts delete-then-insert semantics reset unmentioned columns to defaults, which is not what the engine's upsert does. Use INSERT … ON DUPLICATE KEY UPDATE, which is translated.
INSERT IGNORESilently swallowing errors is not a behaviour we will emulate. Handle the conflict explicitly.
Multi-table DMLDELETE t1, t2 FROM …, DELETE … USING …, DELETE … ORDER BY / LIMIT and joined UPDATEs. The engine models single-table DML; a DELETE bounded by WHERE works.
GROUP_CONCAT, SUBSTRING_INDEXNo engine equivalent to map them onto. Most other common scalar functions are translated or pass through unchanged.
User variables (@x)The engine has nowhere to keep session-scoped user variables. Keep the value in your application.
Multi-statement queriesSend one statement per query. Do not enable your driver's multi-statement option.
Double-quoted strings"x" is parsed as an identifier, not a string. Spell strings with single quotes - 'x' - which is what clients do anyway.
Chunked parameter sendsA prepared statement whose parameter was streamed in chunks is refused rather than executed with an incomplete value. Bind the value in one go.

Trademark

MySQL is a registered trademark of Oracle Corporation and/or its affiliates. OriginChainDB is not affiliated with, endorsed by, or sponsored by Oracle Corporation. OriginChainDB implements a compatible wire protocol so that existing MySQL clients can connect to it; it does not distribute MySQL software.

© 2026 Silicoyn Technologies Pvt Ltd

On this page