// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// What a backend can do beyond the four operations of the `Driver` contract.
///
/// The base contract is deliberately the intersection every relational backend
/// offers. Everything else — a prepared handle, a server-side cursor, `COPY`, more
/// than one result set from one statement, an unsigned 64-bit column — is a
/// capability some backends have and others do not. A query layer asks once,
/// through `QueryExecutor::capabilities`, and lowers its plan instead of probing
/// by running a statement and interpreting the failure.
///
/// The presets below describe what the drivers in this repository implement, not
/// what the server software could theoretically do: `streaming_copy` is true only
/// where a driver actually exposes a streaming source and sink.
///
/// ```moonbit check
/// test "a preset names its own dialect" {
///   let pg = @moondb.Capabilities::for_dialect(@moondb.Dialect::Postgres)
///   assert_eq(pg.dialect.name(), "postgres")
///   assert_true(pg.streaming_copy)
///   assert_false(pg.unsigned_integers)
/// }
/// ```
#derive(Eq)
pub(all) struct Capabilities {
  /// The dialect this report describes.
  dialect : Dialect
  /// A `Stmt` handle the server prepares once and re-executes with new values.
  prepared_statements : Bool
  /// `:name` parameters are rewritten by the driver before the statement is sent.
  named_parameters : Bool
  /// A cursor the server holds open and hands out in batches.
  server_cursors : Bool
  /// `COPY` in both directions is streamed chunk by chunk.
  streaming_copy : Bool
  /// `LISTEN` / `NOTIFY` exist, so a subscriber can wait for a change instead of
  /// polling for one. PostgreSQL has them; the servers that speak its protocol do
  /// not all do, which is why each such driver reports its own answer.
  notifications : Bool
  /// `begin` / `commit` / `rollback` bracket real work. An engine without
  /// transactions accepts the calls and does nothing, so a query layer must know
  /// before it wraps a multi-statement unit of work in one.
  transactions : Bool
  /// Nested transactions roll back to a savepoint instead of the whole transaction.
  savepoints : Bool
  /// One statement can return more than one result set.
  multiple_result_sets : Bool
  /// An unsigned 64-bit column keeps its value above 2^63-1.
  unsigned_integers : Bool
  /// `INSERT`/`UPDATE`/`DELETE` accept a `RETURNING` clause.
  returning : Bool
}

///|
/// The capabilities of the driver moondb ships for `dialect`.
pub fn Capabilities::for_dialect(dialect : Dialect) -> Capabilities {
  match dialect {
    Sqlite =>
      {
        dialect,
        prepared_statements: true,
        named_parameters: true,
        server_cursors: false,
        streaming_copy: false,
        notifications: false,
        transactions: true,
        savepoints: true,
        multiple_result_sets: false,
        unsigned_integers: false,
        returning: true,
      }
    Postgres =>
      {
        dialect,
        prepared_statements: true,
        named_parameters: true,
        server_cursors: true,
        streaming_copy: true,
        notifications: true,
        transactions: true,
        savepoints: true,
        multiple_result_sets: true,
        unsigned_integers: false,
        returning: true,
      }
    Mysql =>
      {
        dialect,
        prepared_statements: true,
        named_parameters: true,
        server_cursors: false,
        streaming_copy: false,
        notifications: false,
        transactions: true,
        savepoints: true,
        multiple_result_sets: true,
        unsigned_integers: true,
        returning: false,
      }
    // ClickHouse is an analytical engine: one statement, one result, no
    // transactions to nest and no server-side cursor to hold open. What it does have
    // is anonymous (`?`) binding, which its driver rewrites into its own parameter
    // syntax, and 64-bit unsigned columns.
    // DuckDB is an embedded analytical engine with real transactions and no
    // savepoints; it has `RETURNING` and unsigned 64-bit columns, and one statement
    // still returns one result set.
    Duckdb =>
      {
        dialect,
        prepared_statements: true,
        named_parameters: true,
        server_cursors: false,
        streaming_copy: false,
        notifications: false,
        transactions: true,
        savepoints: false,
        multiple_result_sets: false,
        unsigned_integers: true,
        returning: true,
      }
    // SQL Server binds parameters out of band through `sp_executesql` and compiles a text
    // once into a handle with `sp_prepare`, which `sp_execute` then re-runs with new
    // values. It nests transactions through savepoints and has an `OUTPUT` clause where the
    // others have `RETURNING`. It has no server-side cursor of the `DECLARE`/`FETCH` kind
    // this flag means, and no unsigned 64-bit column.
    Sqlserver =>
      {
        dialect,
        prepared_statements: true,
        named_parameters: true,
        server_cursors: false,
        streaming_copy: false,
        notifications: false,
        transactions: true,
        savepoints: true,
        multiple_result_sets: true,
        unsigned_integers: false,
        returning: true,
      }
    Clickhouse =>
      {
        dialect,
        prepared_statements: false,
        named_parameters: true,
        server_cursors: false,
        streaming_copy: false,
        notifications: false,
        transactions: false,
        savepoints: false,
        multiple_result_sets: false,
        unsigned_integers: true,
        returning: false,
      }
  }
}

///|
/// Whether this report describes `dialect`.
pub fn Capabilities::is_for(self : Capabilities, dialect : Dialect) -> Bool {
  self.dialect == dialect
}

///|
/// The number of capabilities the backend has, which is the useful shape for a
/// feature matrix in a test: a preset that loses one silently stops adding up.
pub fn Capabilities::count(self : Capabilities) -> Int32 {
  let flags = [
    self.prepared_statements,
    self.named_parameters,
    self.server_cursors,
    self.streaming_copy,
    self.notifications,
    self.transactions,
    self.savepoints,
    self.multiple_result_sets,
    self.unsigned_integers,
    self.returning,
  ]
  let mut n = 0
  for flag in flags {
    if flag {
      n += 1
    }
  }
  n
}

///|
pub extend Capabilities with Eq::{not_equal, equal}