From f105e8bb095e0be309aa434868eb85b8138b8c4d Mon Sep 17 00:00:00 2001 From: Steven Niu Date: Wed, 12 Aug 2026 01:09:23 +0000 Subject: [PATCH 1/2] add doc for dbtimezone feature --- CN/modules/ROOT/nav.adoc | 2 + .../dbtimezone_impl.adoc | 310 +++++++++++++++++ .../compat_dbtimezone.adoc | 145 ++++++++ EN/modules/ROOT/nav.adoc | 2 + .../dbtimezone_impl_en.adoc | 316 ++++++++++++++++++ .../compat_dbtimezone_en.adoc | 145 ++++++++ 6 files changed, 920 insertions(+) create mode 100644 CN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl.adoc create mode 100644 CN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone.adoc create mode 100644 EN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl_en.adoc create mode 100644 EN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone_en.adoc diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index c6a77b59..bb4fb7e3 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -31,6 +31,7 @@ ** xref:master/oracle_compatibility/compat_create_index_online.adoc[22、索引 ONLINE 参数] ** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG 函数] ** xref:master/oracle_compatibility/compat_alter_index_unusable.adoc[24、禁用索引] +** xref:master/oracle_compatibility/compat_dbtimezone.adoc[25、dbtimezone] * 容器化与云服务 ** 容器化指南 *** xref:master/containerization/k8s_deployment.adoc[K8S部署] @@ -109,6 +110,7 @@ **** xref:master/compatibility_features_design/with_function_procedure_impl.adoc[WITH FUNCTION/PROCEDURE] **** xref:master/compatibility_features_design/create_index_online.adoc[索引 ONLINE 参数] **** xref:master/compatibility_features_design/alter_index_unusable_impl.adoc[禁用索引] +**** xref:master/compatibility_features_design/dbtimezone_impl.adoc[dbtimezone] *** 内置函数 **** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context] **** xref:master/oracle_builtin_functions/userenv.adoc[userenv] diff --git a/CN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl.adoc b/CN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl.adoc new file mode 100644 index 00000000..a22ed1aa --- /dev/null +++ b/CN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl.adoc @@ -0,0 +1,310 @@ +:sectnums: +:sectnumlevels: 5 + += DBTIMEZONE 实现说明 + +== 目的 + +本文档详细说明 IvorySQL 中 `DBTIMEZONE` 函数功能的实现原理。该功能提供一个数据库级、非会话级的固定时区值,通过 PostgreSQL 原生的 `ALTER DATABASE ... SET` 机制持久化,实现 Oracle 数据库 `DBTIMEZONE` 函数的语义。 + +== 实现说明 + +=== 系统分层架构 + +`DBTIMEZONE` 的实现分为四个层次,均位于 `contrib/ivorysql_ora`: + +``` +┌───────────────────────────────────────────────────────────┐ +│ Layer 1: GUC 定义与权限层 (src/guc/guc.c + src/include/guc.h)│ +│ ─ 新增自定义 GUC ivorysql.dbtimezone(PGC_SUSET) │ +│ ─ check_dbtimezone():按 GucSource 拒绝会话内 SET/ALTER ROLE,│ +│ 只允许 ALTER DATABASE ... SET;并校验偏移/区域名格式 │ +└───────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────┐ +│ Layer 2: 命令层拦截 (src/ivorysql_ora.c) │ +│ ─ ivorysql_ora_ProcessUtility()(既有 ProcessUtility_hook)│ +│ 新增 reject_alter_role_dbtimezone():在解析树层面直接 │ +│ 拦截 ALTER ROLE ... SET/ALTER ROLE ALL SET,早于 Layer 1 │ +│ 的 check hook 生效,命令本身直接报错,不留 catalog 残留 │ +└───────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────┐ +│ Layer 3: C 函数层 (src/builtin_functions/ │ +│ datetime_datatype_functions.c) │ +│ ─ ora_dbtimezone():读取 ivorysql_dbtimezone 变量并返回 text │ +│ 与既有的 ora_sessiontimezone()(读取 session_timezone) │ +│ 紧邻,实现方式一致、语义刻意区分 │ +└───────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────┐ +│ Layer 4: SQL 目录层 │ +│ (src/builtin_functions/builtin_functions--1.0.sql) │ +│ ─ CREATE FUNCTION sys.dbtimezone() ... STABLE │ +│ 紧邻既有的 sys.sessiontimezone() │ +└───────────────────────────────────────────────────────────┘ +``` + +本功能没有新增语法(不需要 Oracle 解析器/AST/目录列层面的改动)——`dbtimezone()` 是一个普通的 `STABLE` SQL 函数,配合一个自定义 GUC,复用 PostgreSQL 已有的 per-database 配置机制即可实现。 + +=== 设计方案:新增 GUC + +GUC 本身不是"per-database 专属存储",而是复用了 PostgreSQL 对任意 GUC 都支持的 `ALTER DATABASE/ROLE ... SET` 通用机制(持久化在 `pg_db_role_setting` 系统表),没有为 `DBTIMEZONE` 单独设计目录字段。 + +=== GUC 定义与权限模型 + +==== 命名规范 + +GUC 名为 `ivorysql.dbtimezone`,与项目现有自定义 GUC 命名惯例保持一致。对应的 C 端变量为 `ivorysql_dbtimezone`。 + +==== 权限模型:只能通过 ALTER DATABASE 设置 + +`ALTER DATABASE dbname SET = value` 实际上有两层独立的权限检查:数据库对象本身的权限(是否有权 `ALTER` 这个库,owner 或超级用户即可)与 GUC 参数本身的权限(是否允许"设置"这个参数,与是否拥有该数据库无关)。`ivorysql.dbtimezone` 的 `context` 设为 `PGC_SUSET`,因此第二层默认只有超级用户能通过;若需要委派给普通角色,超级用户需额外执行 `GRANT SET ON PARAMETER ivorysql.dbtimezone TO ;`(对应 PostgreSQL 15+ 引入的 `pg_parameter_acl` 目录)。 + +仅设置 `context = PGC_SUSET` 不足以区分"会话内 `SET`"和"`ALTER DATABASE ... SET`"——两者内部同样以 `PGC_SUSET` 身份调用 `set_config_option()`。真正能区分调用来源的是 check hook 收到的 `GucSource source` 参数: + +[cols="2,3,1"] +|=== +| `GucSource` | 触发场景 | 是否允许 + +| `PGC_S_SESSION` +| 会话内 `SET ivorysql.dbtimezone = ...;` +| 拒绝 + +| `PGC_S_USER` +| `ALTER ROLE rolename SET ...`(不区分数据库) +| 拒绝 + +| `PGC_S_DATABASE_USER` +| `ALTER ROLE rolename IN DATABASE dbname SET ...` +| 拒绝 + +| `PGC_S_CLIENT` +| 客户端连接选项(如 `PGOPTIONS`) +| 拒绝 + +| `PGC_S_GLOBAL` +| `ALTER ROLE ALL SET ...`(全局默认) +| 拒绝 + +| `PGC_S_DATABASE` +| `ALTER DATABASE dbname SET ...` 在连接建立时生效 +| 允许 + +| `PGC_S_TEST` +| 执行 `ALTER DATABASE ... SET` 命令本身时的校验阶段 +| 允许(否则命令本身都执行不了) + +| `PGC_S_DEFAULT` / `PGC_S_DYNAMIC_DEFAULT` / `PGC_S_FILE` / `PGC_S_ARGV` / `PGC_S_ENV_VAR` +| 启动默认值、`postgresql.conf`、`postmaster` 命令行/环境变量 +| 允许(保证集群启动不受影响) + +| `PGC_S_OVERRIDE` +| 重放一个已校验过的值(如并行 worker 同步) +| 允许(否则并行查询会报错) +|=== + +==== `check_dbtimezone()` 实现 + +`contrib/ivorysql_ora/src/guc/guc.c`: + +[source,c] +---- +/* Backing variable for ivorysql.dbtimezone, read by dbtimezone(). */ +char *ivorysql_dbtimezone = NULL; + +static bool +check_dbtimezone(char **newval, void **extra, GucSource source) +{ + char *str = *newval; + + if (source == PGC_S_SESSION || + source == PGC_S_USER || + source == PGC_S_DATABASE_USER || + source == PGC_S_CLIENT || + source == PGC_S_GLOBAL) + { + GUC_check_errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM); + GUC_check_errmsg("parameter \"ivorysql.dbtimezone\" cannot be set"); + GUC_check_errdetail("\"ivorysql.dbtimezone\" can only be set with " + "ALTER DATABASE ... SET, not within a session " + "or per-role."); + return false; + } + + /* [+-]HH:MI 格式校验,范围 -12:59 ~ +14:00(与 Oracle 一致) */ + if (strlen(str) == 6 && ... ) + { + ... + } + + /* 否则必须是合法的时区区域名(复用 pg_tzset() 校验) */ + if (!pg_tzset(str)) + { + GUC_check_errdetail("\"%s\" is not a valid UTC offset (+/-HH:MI) " + "or time zone name.", str); + return false; + } + + return true; +} + +void +IvorysqlOraDefineGucs(void) +{ + DefineCustomStringVariable("ivorysql.dbtimezone", + "Sets the database time zone reported by dbtimezone().", + "Can only be set with ALTER DATABASE ... SET, not with a " + "plain SET or ALTER ROLE ... SET. Requires superuser, or a " + "role granted permission via " + "GRANT SET ON PARAMETER ivorysql.dbtimezone TO .", + &ivorysql_dbtimezone, + "+00:00", + PGC_SUSET, + 0, + check_dbtimezone, + NULL, + NULL); +} +---- + +`GUC_check_errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM)` + `GUC_check_errmsg(...)` 把会话内 `SET` 被拒绝时的报错改成 `parameter "ivorysql.dbtimezone" cannot be set`,而非泛用的 `invalid value for parameter ...: "..."` ——这类拒绝的原因是"这个参数不能这样设置"而不是"这个值不合法",用专门的 errcode/errmsg 更准确地表达语义;格式/范围校验失败(`GUC_check_errdetail` 但不设 `GUC_check_errmsg`)则仍走默认的 `invalid value for parameter` 提示。 + +=== 命令层拦截:`ALTER ROLE ... SET` + +`contrib/ivorysql_ora/src/ivorysql_ora.c` 已有一个 `ProcessUtility_hook`,在其中新增: + +[source,c] +---- +static void +reject_alter_role_dbtimezone(Node *parsetree) +{ + AlterRoleSetStmt *stmt; + VariableSetStmt *setstmt; + + if (nodeTag(parsetree) != T_AlterRoleSetStmt) + return; + + stmt = (AlterRoleSetStmt *) parsetree; + setstmt = stmt->setstmt; + + if (setstmt == NULL || setstmt->name == NULL) + return; /* RESET ALL, or malformed */ + + if (setstmt->kind == VAR_RESET || setstmt->kind == VAR_RESET_ALL) + return; /* clearing an override is always fine */ + + if (pg_strcasecmp(setstmt->name, "ivorysql.dbtimezone") == 0) + ereport(ERROR, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"ivorysql.dbtimezone\" cannot be set"), + errdetail("\"ivorysql.dbtimezone\" can only be set with " + "ALTER DATABASE ... SET, not within a session " + "or per-role."), + errhint("Use ALTER DATABASE ... SET ivorysql.dbtimezone " + "instead, or ALTER ROLE ... RESET " + "ivorysql.dbtimezone to remove a stale per-role " + "override."))); +} +---- + +并在 `ivorysql_ora_ProcessUtility()` 里,调用 `standard_ProcessUtility()`(或上一个已安装的 hook)之前插入这个检查——命令一旦匹配就直接 `ereport(ERROR, ...)`,`ALTER ROLE` 不会被执行到写 catalog 那一步。 + +=== SQL 函数层 + +==== `ora_dbtimezone()` C 函数 + +`contrib/ivorysql_ora/src/builtin_functions/datetime_datatype_functions.c`,紧邻既有的 `ora_sessiontimezone()`: + +[source,c] +---- +/* + * returns the time zone of the database, as set by + * ivorysql.dbtimezone. Unlike sessiontimezone(), this value is + * independent of the session's TimeZone setting. + */ +Datum +ora_dbtimezone(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text(ivorysql_dbtimezone)); +} +---- + +对比 `ora_sessiontimezone()` 读取的是 `session_timezone`(会话级 `pg_tz *`),`ora_dbtimezone()` 直接读取 Layer 1 定义的 GUC 字符串。 + +==== 目录声明 `sys.dbtimezone()` + +`contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql`,紧邻 `sys.sessiontimezone()`: + +[source,sql] +---- +CREATE FUNCTION sys.dbtimezone() +RETURNS text +AS 'MODULE_PATHNAME','ora_dbtimezone' +LANGUAGE C +STRICT +STABLE; +---- + +标记为 `STABLE` 而非 `IMMUTABLE`:返回值可能因 `ALTER DATABASE ... SET` 而改变(虽然一次连接内不会变),与 `sessiontimezone()` 的标记方式保持一致。该函数最终随扩展脚本 `ivorysql_ora--1.0.sql`分发。 + +=== 与 PG_PARSER 的关系 + +不同于 `ALTER INDEX ... UNUSABLE` 那种只存在于 Oracle 语法层的新语句,`dbtimezone()` 是普通的 SQL 函数,语法上不受 `compatible_db`/`ivorysql.compatible_mode` 限制:任何解析模式下都可以用 `sys.dbtimezone()` 显式限定调用;只有以裸函数名 `dbtimezone()` 调用(依赖 `search_path` 能解析到 `sys` 模式)时才与 Oracle 兼容模式的 search_path 行为相关,这属于 `sys` schema 本身的可见性问题,非本功能特有。 + +== 错误处理 + +=== 会话内 SET 被拒绝 + +[source,sql] +---- +SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +---- +错误由 `check_dbtimezone()` 中 `source == PGC_S_SESSION` 分支主动抛出。 + +=== ALTER ROLE ... SET 在命令层直接被拒绝 + +[source,sql] +---- +ALTER ROLE myrole IN DATABASE mydb SET ivorysql.dbtimezone = '+09:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +-- HINT: Use ALTER DATABASE ... SET ivorysql.dbtimezone instead, or ALTER ROLE ... RESET ivorysql.dbtimezone to remove a stale per-role override. + +ALTER ROLE myrole SET ivorysql.dbtimezone = '+09:00'; -- 同样报错 +ALTER ROLE ALL SET ivorysql.dbtimezone = '+09:00'; -- 同样报错 + +ALTER ROLE myrole IN DATABASE mydb RESET ivorysql.dbtimezone; -- OK,不受影响 +---- +错误由 `reject_alter_role_dbtimezone()`(Layer 2,`ivorysql_ora.c` 的 `ProcessUtility_hook`)在解析树层面直接抛出,早于命令真正执行、早于 `pg_db_role_setting` 被写入。详见上文"命令层拦截"小节。 + +=== 非法值 / 超出范围偏移在 ALTER DATABASE 阶段即报错 + +[source,sql] +---- +ALTER DATABASE mydb SET ivorysql.dbtimezone = 'not_a_zone'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "not_a_zone" +-- DETAIL: "not_a_zone" is not a valid UTC offset (+/-HH:MI) or time zone name. + +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+15:00'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "+15:00" +-- DETAIL: time zone offset "+15:00" is out of range for DBTIMEZONE (-12:59 to +14:00) +---- +错误来自 `check_dbtimezone()` 的偏移/区域名格式校验分支,走默认的 `invalid value for parameter` 文案(未设置 `GUC_check_errmsg`)。 + +=== 未授权的普通用户执行 ALTER DATABASE + +[source,sql] +---- +\c mydb normal_user +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: permission denied to set parameter "ivorysql.dbtimezone" +---- +`context = PGC_SUSET` 且 `normal_user` 未被 `GRANT SET ON PARAMETER` 授权,权限检查在到达 `check_dbtimezone()` 之前就失败,因此报错文案是 PostgreSQL 通用的 GUC 权限错误,而非本功能自定义的错误信息。 + +== 已知限制 + +1. **未走 Oracle 的 `CREATE DATABASE ... TIME_ZONE` 语法**:当前只能通过 PostgreSQL 原生的 `ALTER DATABASE ... SET` 设置,没有在 IvorySQL 的 Oracle 语法层(`ora_gram.y`)增加对应的 `CREATE/ALTER DATABASE ... SET TIME_ZONE` 关键字兼容写法。 +2. **偏移 / 区域名格式未做精细区分**:Oracle 实际上对 `DBTIMEZONE`(数据库级)和 `SESSIONTIMEZONE`/`TIME_ZONE`(会话级)在偏移与区域名的允许范围上有细节差异,本实现为简化起见统一按"偏移或区域名皆可"处理。 diff --git a/CN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone.adoc b/CN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone.adoc new file mode 100644 index 00000000..0bfc8138 --- /dev/null +++ b/CN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone.adoc @@ -0,0 +1,145 @@ +:sectnums: +:sectnumlevels: 5 + += DBTIMEZONE + +== 目的 + +本文档解释 IvorySQL 中 `DBTIMEZONE` 函数的用途,实现 Oracle 风格的数据库级时区查询功能。 + +Oracle 提供两个时区相关的内置函数:`SESSIONTIMEZONE` 返回当前会话的时区(随 `ALTER SESSION SET TIME_ZONE` 改变),`DBTIMEZONE` 返回数据库级别固定的时区(在 `CREATE DATABASE` / `ALTER DATABASE SET TIME_ZONE` 时确定,不随会话变化)。IvorySQL 已实现 `SESSIONTIMEZONE`,本功能补齐 `DBTIMEZONE`。 + +== 功能说明 + +=== 基本语法 + +[source,sql] +---- +SELECT dbtimezone() FROM dual; +---- + +返回一个 `text` 类型的时区值,格式为 UTC 偏移(`[+-]HH:MI`),默认值为 `'+00:00'`。 + +=== 核心特性 + +- **数据库级、非会话级**:返回值由 `ALTER DATABASE ... SET ivorysql.dbtimezone = ...` 固定,不受当前会话 `SET timezone` 影响,与 `SESSIONTIMEZONE`(会话级)形成对照 +- **仅能通过 `ALTER DATABASE ... SET` 修改**:会话内直接 `SET ivorysql.dbtimezone = ...`、以及任何形式的 `ALTER ROLE ... SET ivorysql.dbtimezone = ...`(`ALTER ROLE rolename SET`、`ALTER ROLE rolename IN DATABASE dbname SET`、`ALTER ROLE ALL SET`)都会在命令层直接报错,不会被静默接受;只有 `ALTER DATABASE dbname SET ivorysql.dbtimezone = ...` 才会在下次连接该库时生效 +- **默认仅超级用户可设置**:普通用户即使是自己创建/拥有的数据库的 owner,也无权执行 `ALTER DATABASE ... SET ivorysql.dbtimezone = ...`;超级用户可以通过 `GRANT SET ON PARAMETER ivorysql.dbtimezone TO ;` 显式授权某个非超级用户角色管理该设置 +- **同一数据库下所有角色看到同一个值**:`DBTIMEZONE` 是纯数据库属性,不支持"同一数据库、不同角色看到不同值"这种用法——任何试图按角色区分的 `ALTER ROLE ... SET` 都会在命令层直接被拒绝(`ALTER ROLE ... RESET` 除外,用于清理历史遗留的记录) +- **值格式校验**:接受 `[+-]HH:MI` 形式的 UTC 偏移,范围 `-12:59` ~ `+14:00`(与 Oracle 一致),或合法的 IANA 时区区域名 + +== 语法示例 + +=== 基本用法 + +[source,sql] +---- +SELECT dbtimezone() FROM dual; +-- dbtimezone +-- ------------ +-- +00:00 +-- (1 row) +---- + +=== 与 SESSIONTIMEZONE 对比:不受会话时区影响 + +[source,sql] +---- +SET timezone = 'Asia/Hong_Kong'; +SELECT sessiontimezone() FROM dual; +-- sessiontimezone +-- ----------------- +-- Asia/Hong_Kong + +SELECT dbtimezone() FROM dual; +-- 仍为数据库固定值,不受上面 SET timezone 影响 +-- dbtimezone +-- ------------ +-- +00:00 +---- + +=== 通过 ALTER DATABASE 固定某个数据库的时区 + +[source,sql] +---- +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+08:00'; +-- 需要断开重连(新会话)后才生效 +\c mydb +SELECT dbtimezone() FROM dual; -- +08:00 +---- + +=== 授权非超级用户管理自己数据库的 DBTIMEZONE + +[source,sql] +---- +-- 超级用户执行一次性授权: +GRANT SET ON PARAMETER ivorysql.dbtimezone TO app_owner; + +-- app_owner 之后可以为自己拥有的数据库设置: +\c app_db app_owner +ALTER DATABASE app_db SET ivorysql.dbtimezone = '+05:30'; +\c app_db app_owner +SELECT dbtimezone() FROM dual; -- +05:30 +---- + +== 错误处理 + +=== 会话内直接 SET 被拒绝 + +[source,sql] +---- +SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +---- + +=== ALTER ROLE ... SET 在命令层直接被拒绝 + +[source,sql] +---- +ALTER ROLE myrole IN DATABASE mydb SET ivorysql.dbtimezone = '+09:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +-- HINT: Use ALTER DATABASE ... SET ivorysql.dbtimezone instead, or ALTER ROLE ... RESET ivorysql.dbtimezone to remove a stale per-role override. + +ALTER ROLE myrole SET ivorysql.dbtimezone = '+09:00'; -- 同样报错 +ALTER ROLE ALL SET ivorysql.dbtimezone = '+09:00'; -- 同样报错 + +-- RESET / RESET ALL 不受影响,仍可用于清理历史遗留的记录: +ALTER ROLE myrole IN DATABASE mydb RESET ivorysql.dbtimezone; -- OK +---- + +=== 非法值 / 超出范围偏移 + +[source,sql] +---- +ALTER DATABASE mydb SET ivorysql.dbtimezone = 'not_a_zone'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "not_a_zone" +-- DETAIL: "not_a_zone" is not a valid UTC offset (+/-HH:MI) or time zone name. + +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+15:00'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "+15:00" +-- DETAIL: time zone offset "+15:00" is out of range for DBTIMEZONE (-12:59 to +14:00) +---- + +=== 未被授权的普通用户执行 ALTER DATABASE + +[source,sql] +---- +-- normal_user 是 mydb 的 owner,但没有被 GRANT SET ON PARAMETER 授权 +\c mydb normal_user +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: permission denied to set parameter "ivorysql.dbtimezone" +---- + +NOTE: 拥有/创建了一个数据库不代表对这个数据库能设置的每个 GUC 参数都有权限——数据库级权限(是否能 `ALTER` 这个库)与 GUC 参数级权限(是否能"设置"这个参数)是两层独立的检查。 + +== 清理 + +[source,sql] +---- +-- 恢复默认值: +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+00:00'; +-- 或者完全移除该数据库的自定义设置,回落到集群默认值: +ALTER DATABASE mydb RESET ivorysql.dbtimezone; +---- diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index 78185f6f..1aa096ee 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -31,6 +31,7 @@ ** xref:master/oracle_compatibility/compat_create_index_online.adoc[22、ONLINE Parameter for CREATE INDEX] ** xref:master/oracle_compatibility/compat_stragg.adoc[23、STRAGG function] ** xref:master/oracle_compatibility/compat_alter_index_unusable_en.adoc[24、Alter Index Unusable] +** xref:master/oracle_compatibility/compat_dbtimezone_en.adoc[24、dbtimezone] * Containerization and Cloud Service ** Containerization *** xref:master/containerization/k8s_deployment.adoc[K8S deployment] @@ -109,6 +110,7 @@ *** xref:master/compatibility_features_design/with_function_procedure_impl_en.adoc[WITH FUNCTION/PROCEDURE] *** xref:master/compatibility_features_design/create_index_online.adoc[ONLINE Parameter for CREATE INDEX] *** xref:master/compatibility_features_design/alter_index_unusable_impl_en.adoc[Alter Index Unusable] +*** xref:master/compatibility_features_design/dbtimezone_impl_en.adoc[dbtimezone] ** Built-in Functions *** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context] *** xref:master/oracle_builtin_functions/userenv.adoc[userenv] diff --git a/EN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl_en.adoc b/EN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl_en.adoc new file mode 100644 index 00000000..7a5206f9 --- /dev/null +++ b/EN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl_en.adoc @@ -0,0 +1,316 @@ +:sectnums: +:sectnumlevels: 5 + += DBTIMEZONE Implementation Notes + +== Purpose + +This document describes in detail the implementation principles behind the `DBTIMEZONE` function feature in IvorySQL. This feature provides a database-level, non-session-level fixed time zone value, persisted through PostgreSQL's native `ALTER DATABASE ... SET` mechanism, implementing the semantics of Oracle's `DBTIMEZONE` function. + +== Implementation Notes + +=== System Layering Architecture + +The implementation of `DBTIMEZONE` is divided into four layers, all located under `contrib/ivorysql_ora`: + +``` +┌───────────────────────────────────────────────────────────────┐ +│ Layer 1: GUC Definition & Permission Layer │ +│ (src/guc/guc.c + src/include/guc.h) │ +│ ─ Adds the custom GUC ivorysql.dbtimezone (PGC_SUSET) │ +│ ─ check_dbtimezone(): rejects in-session SET/ALTER ROLE based │ +│ on GucSource, allowing only ALTER DATABASE ... SET; also │ +│ validates the offset/region-name format │ +└───────────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────────┐ +│ Layer 2: Command-Level Interception (src/ivorysql_ora.c) │ +│ ─ ivorysql_ora_ProcessUtility() (an existing │ +│ ProcessUtility_hook) adds │ +│ reject_alter_role_dbtimezone(): intercepts │ +│ ALTER ROLE ... SET / ALTER ROLE ALL SET directly at the │ +│ parse-tree level, ahead of the Layer 1 check hook, so the │ +│ command itself errors out immediately with no catalog │ +│ residue left behind │ +└───────────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────────┐ +│ Layer 3: C Function Layer │ +│ (src/builtin_functions/datetime_datatype_functions.c) │ +│ ─ ora_dbtimezone(): reads the ivorysql_dbtimezone variable │ +│ and returns it as text; placed right next to the existing │ +│ ora_sessiontimezone() (which reads session_timezone) — │ +│ same implementation style, deliberately distinct semantics │ +└───────────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────────┐ +│ Layer 4: SQL Catalog Layer │ +│ (src/builtin_functions/builtin_functions--1.0.sql) │ +│ ─ CREATE FUNCTION sys.dbtimezone() ... STABLE │ +│ placed right next to the existing sys.sessiontimezone() │ +└───────────────────────────────────────────────────────────────┘ +``` + +This feature adds no new syntax (no changes are needed at the Oracle parser/AST/catalog-column level) — `dbtimezone()` is an ordinary `STABLE` SQL function paired with a custom GUC, implemented by reusing PostgreSQL's existing per-database configuration mechanism. + +=== Design Approach: Adding a New GUC + +A GUC itself is not "storage dedicated to per-database values" — rather, it reuses the generic `ALTER DATABASE/ROLE ... SET` mechanism that PostgreSQL supports for any GUC (persisted in the `pg_db_role_setting` system catalog). No dedicated catalog column was designed for `DBTIMEZONE`. + +=== GUC Definition and Permission Model + +==== Naming Convention + +The GUC is named `ivorysql.dbtimezone`, consistent with the project's existing custom-GUC naming convention. The corresponding C-side variable is `ivorysql_dbtimezone`. + +==== Permission Model: Settable Only via ALTER DATABASE + +`ALTER DATABASE dbname SET = value` actually involves two independent permission checks: permission on the database object itself (whether you're allowed to `ALTER` this database — being the owner or a superuser suffices), and permission on the GUC parameter itself (whether "setting" this parameter is allowed, independent of whether you own the database). The `context` of `ivorysql.dbtimezone` is set to `PGC_SUSET`, so by default only superusers pass the second check; to delegate this to an ordinary role, a superuser must additionally run `GRANT SET ON PARAMETER ivorysql.dbtimezone TO ;` (corresponding to the `pg_parameter_acl` catalog introduced in PostgreSQL 15+). + +Setting `context = PGC_SUSET` alone is not enough to distinguish "in-session `SET`" from "`ALTER DATABASE ... SET`" — both internally call `set_config_option()` under the same `PGC_SUSET` identity. What actually distinguishes the call origin is the `GucSource source` parameter received by the check hook: + +[cols="2,3,1"] +|=== +| `GucSource` | Triggering Scenario | Allowed? + +| `PGC_S_SESSION` +| In-session `SET ivorysql.dbtimezone = ...;` +| Rejected + +| `PGC_S_USER` +| `ALTER ROLE rolename SET ...` (not scoped to a database) +| Rejected + +| `PGC_S_DATABASE_USER` +| `ALTER ROLE rolename IN DATABASE dbname SET ...` +| Rejected + +| `PGC_S_CLIENT` +| Client connection options (e.g. `PGOPTIONS`) +| Rejected + +| `PGC_S_GLOBAL` +| `ALTER ROLE ALL SET ...` (global default) +| Rejected + +| `PGC_S_DATABASE` +| `ALTER DATABASE dbname SET ...` taking effect at connection establishment +| Allowed + +| `PGC_S_TEST` +| The validation phase while executing the `ALTER DATABASE ... SET` command itself +| Allowed (otherwise the command itself could never execute) + +| `PGC_S_DEFAULT` / `PGC_S_DYNAMIC_DEFAULT` / `PGC_S_FILE` / `PGC_S_ARGV` / `PGC_S_ENV_VAR` +| Startup default value, `postgresql.conf`, `postmaster` command-line/environment variables +| Allowed (to guarantee cluster startup is unaffected) + +| `PGC_S_OVERRIDE` +| Replaying an already-validated value (e.g. parallel worker synchronization) +| Allowed (otherwise parallel queries would error out) +|=== + +==== `check_dbtimezone()` Implementation + +`contrib/ivorysql_ora/src/guc/guc.c`: + +[source,c] +---- +/* Backing variable for ivorysql.dbtimezone, read by dbtimezone(). */ +char *ivorysql_dbtimezone = NULL; + +static bool +check_dbtimezone(char **newval, void **extra, GucSource source) +{ + char *str = *newval; + + if (source == PGC_S_SESSION || + source == PGC_S_USER || + source == PGC_S_DATABASE_USER || + source == PGC_S_CLIENT || + source == PGC_S_GLOBAL) + { + GUC_check_errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM); + GUC_check_errmsg("parameter \"ivorysql.dbtimezone\" cannot be set"); + GUC_check_errdetail("\"ivorysql.dbtimezone\" can only be set with " + "ALTER DATABASE ... SET, not within a session " + "or per-role."); + return false; + } + + /* Validate [+-]HH:MI format, range -12:59 to +14:00 (consistent with Oracle) */ + if (strlen(str) == 6 && ... ) + { + ... + } + + /* Otherwise it must be a valid time zone region name (reuses pg_tzset() validation) */ + if (!pg_tzset(str)) + { + GUC_check_errdetail("\"%s\" is not a valid UTC offset (+/-HH:MI) " + "or time zone name.", str); + return false; + } + + return true; +} + +void +IvorysqlOraDefineGucs(void) +{ + DefineCustomStringVariable("ivorysql.dbtimezone", + "Sets the database time zone reported by dbtimezone().", + "Can only be set with ALTER DATABASE ... SET, not with a " + "plain SET or ALTER ROLE ... SET. Requires superuser, or a " + "role granted permission via " + "GRANT SET ON PARAMETER ivorysql.dbtimezone TO .", + &ivorysql_dbtimezone, + "+00:00", + PGC_SUSET, + 0, + check_dbtimezone, + NULL, + NULL); +} +---- + +`GUC_check_errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM)` combined with `GUC_check_errmsg(...)` changes the error raised when an in-session `SET` is rejected to `parameter "ivorysql.dbtimezone" cannot be set`, rather than the generic `invalid value for parameter ...: "..."` — the reason for this kind of rejection is "this parameter cannot be set this way," not "this value is invalid," so a dedicated errcode/errmsg expresses the semantics more accurately; failures in the format/range validation (using `GUC_check_errdetail` but not setting `GUC_check_errmsg`) still fall through to the default `invalid value for parameter` message. + +=== Command-Level Interception: `ALTER ROLE ... SET` + +`contrib/ivorysql_ora/src/ivorysql_ora.c` already has a `ProcessUtility_hook`, into which the following is added: + +[source,c] +---- +static void +reject_alter_role_dbtimezone(Node *parsetree) +{ + AlterRoleSetStmt *stmt; + VariableSetStmt *setstmt; + + if (nodeTag(parsetree) != T_AlterRoleSetStmt) + return; + + stmt = (AlterRoleSetStmt *) parsetree; + setstmt = stmt->setstmt; + + if (setstmt == NULL || setstmt->name == NULL) + return; /* RESET ALL, or malformed */ + + if (setstmt->kind == VAR_RESET || setstmt->kind == VAR_RESET_ALL) + return; /* clearing an override is always fine */ + + if (pg_strcasecmp(setstmt->name, "ivorysql.dbtimezone") == 0) + ereport(ERROR, + (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), + errmsg("parameter \"ivorysql.dbtimezone\" cannot be set"), + errdetail("\"ivorysql.dbtimezone\" can only be set with " + "ALTER DATABASE ... SET, not within a session " + "or per-role."), + errhint("Use ALTER DATABASE ... SET ivorysql.dbtimezone " + "instead, or ALTER ROLE ... RESET " + "ivorysql.dbtimezone to remove a stale per-role " + "override."))); +} +---- + +This check is inserted into `ivorysql_ora_ProcessUtility()` before calling `standard_ProcessUtility()` (or the previously installed hook) — as soon as the command matches, it directly calls `ereport(ERROR, ...)`, so `ALTER ROLE` never reaches the point of writing to the catalog. + +=== SQL Function Layer + +==== The `ora_dbtimezone()` C Function + +`contrib/ivorysql_ora/src/builtin_functions/datetime_datatype_functions.c`, placed right next to the existing `ora_sessiontimezone()`: + +[source,c] +---- +/* + * returns the time zone of the database, as set by + * ivorysql.dbtimezone. Unlike sessiontimezone(), this value is + * independent of the session's TimeZone setting. + */ +Datum +ora_dbtimezone(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text(ivorysql_dbtimezone)); +} +---- + +Whereas `ora_sessiontimezone()` reads `session_timezone` (a session-level `pg_tz *`), `ora_dbtimezone()` directly reads the GUC string defined in Layer 1. + +==== Catalog Declaration: `sys.dbtimezone()` + +`contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql`, placed right next to `sys.sessiontimezone()`: + +[source,sql] +---- +CREATE FUNCTION sys.dbtimezone() +RETURNS text +AS 'MODULE_PATHNAME','ora_dbtimezone' +LANGUAGE C +STRICT +STABLE; +---- + +Marked as `STABLE` rather than `IMMUTABLE`: the return value may change due to `ALTER DATABASE ... SET` (even though it will not change within a single connection), consistent with how `sessiontimezone()` is marked. This function is ultimately distributed as part of the extension script `ivorysql_ora--1.0.sql`. + +=== Relationship with PG_PARSER + +Unlike a new statement such as `ALTER INDEX ... UNUSABLE`, which exists only at the Oracle syntax layer, `dbtimezone()` is an ordinary SQL function whose syntax is not restricted by `compatible_db`/`ivorysql.compatible_mode`: `sys.dbtimezone()` can be called explicitly, schema-qualified, under any parsing mode. Only when calling it via the bare function name `dbtimezone()` (relying on `search_path` to resolve to the `sys` schema) does behavior become related to the Oracle-compatible-mode `search_path` behavior — this is a visibility concern of the `sys` schema itself, not something specific to this feature. + +== Error Handling + +=== In-Session SET Is Rejected + +[source,sql] +---- +SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +---- +This error is actively raised by the `source == PGC_S_SESSION` branch in `check_dbtimezone()`. + +=== ALTER ROLE ... SET Is Rejected Directly at the Command Level + +[source,sql] +---- +ALTER ROLE myrole IN DATABASE mydb SET ivorysql.dbtimezone = '+09:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +-- HINT: Use ALTER DATABASE ... SET ivorysql.dbtimezone instead, or ALTER ROLE ... RESET ivorysql.dbtimezone to remove a stale per-role override. + +ALTER ROLE myrole SET ivorysql.dbtimezone = '+09:00'; -- Also errors +ALTER ROLE ALL SET ivorysql.dbtimezone = '+09:00'; -- Also errors + +ALTER ROLE myrole IN DATABASE mydb RESET ivorysql.dbtimezone; -- OK, unaffected +---- +This error is raised directly at the parse-tree level by `reject_alter_role_dbtimezone()` (Layer 2, the `ProcessUtility_hook` in `ivorysql_ora.c`), before the command actually executes and before `pg_db_role_setting` is written to. See the "Command-Level Interception" section above for details. + +=== Invalid Value / Out-of-Range Offset Errors Out at the ALTER DATABASE Stage + +[source,sql] +---- +ALTER DATABASE mydb SET ivorysql.dbtimezone = 'not_a_zone'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "not_a_zone" +-- DETAIL: "not_a_zone" is not a valid UTC offset (+/-HH:MI) or time zone name. + +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+15:00'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "+15:00" +-- DETAIL: time zone offset "+15:00" is out of range for DBTIMEZONE (-12:59 to +14:00) +---- +This error comes from the offset/region-name format validation branch of `check_dbtimezone()`, which falls through to the default `invalid value for parameter` message (since `GUC_check_errmsg` is not set). + +=== An Unauthorized Ordinary User Executing ALTER DATABASE + +[source,sql] +---- +\c mydb normal_user +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: permission denied to set parameter "ivorysql.dbtimezone" +---- +Since `context = PGC_SUSET` and `normal_user` has not been granted permission via `GRANT SET ON PARAMETER`, the permission check fails before `check_dbtimezone()` is even reached, so the error message is PostgreSQL's generic GUC permission error, not a custom error message from this feature. + +== Known Limitations + +1. **Does not go through Oracle's `CREATE DATABASE ... TIME_ZONE` syntax**: Currently the value can only be set via PostgreSQL's native `ALTER DATABASE ... SET`; no corresponding `CREATE/ALTER DATABASE ... SET TIME_ZONE` compatible keyword syntax has been added at IvorySQL's Oracle syntax layer (`ora_gram.y`). +2. **No fine-grained distinction between offset and region-name formats**: Oracle actually has subtle differences in the allowed ranges of offsets and region names between `DBTIMEZONE` (database-level) and `SESSIONTIMEZONE`/`TIME_ZONE` (session-level); for simplicity, this implementation treats "either an offset or a region name" uniformly. diff --git a/EN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone_en.adoc b/EN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone_en.adoc new file mode 100644 index 00000000..f1dc236a --- /dev/null +++ b/EN/modules/ROOT/pages/master/oracle_compatibility/compat_dbtimezone_en.adoc @@ -0,0 +1,145 @@ +:sectnums: +:sectnumlevels: 5 + += DBTIMEZONE + +== Purpose + +This document explains the purpose of the `DBTIMEZONE` function in IvorySQL, which implements Oracle-style database-level time zone querying. + +Oracle provides two time-zone-related built-in functions: `SESSIONTIMEZONE` returns the time zone of the current session (changed via `ALTER SESSION SET TIME_ZONE`), and `DBTIMEZONE` returns a fixed database-level time zone (determined at `CREATE DATABASE` / `ALTER DATABASE SET TIME_ZONE` time, independent of the session). IvorySQL has already implemented `SESSIONTIMEZONE`; this feature completes `DBTIMEZONE`. + +== Feature Description + +=== Basic Syntax + +[source,sql] +---- +SELECT dbtimezone() FROM dual; +---- + +Returns a `text`-typed time zone value in UTC offset format (`[+-]HH:MI`), with a default value of `'+00:00'`. + +=== Core Characteristics + +- **Database-level, not session-level**: The return value is fixed by `ALTER DATABASE ... SET ivorysql.dbtimezone = ...`, and is not affected by the current session's `SET timezone`, in contrast to `SESSIONTIMEZONE` (session-level) +- **Can only be modified via `ALTER DATABASE ... SET`**: Setting `SET ivorysql.dbtimezone = ...` directly within a session, and any form of `ALTER ROLE ... SET ivorysql.dbtimezone = ...` (`ALTER ROLE rolename SET`, `ALTER ROLE rolename IN DATABASE dbname SET`, `ALTER ROLE ALL SET`), will raise an error directly at the command level rather than being silently accepted; only `ALTER DATABASE dbname SET ivorysql.dbtimezone = ...` takes effect, and only on the next connection to that database +- **Only superusers can set it by default**: An ordinary user cannot execute `ALTER DATABASE ... SET ivorysql.dbtimezone = ...` even if they are the owner of a database they created themselves; a superuser can explicitly grant a non-superuser role permission to manage this setting via `GRANT SET ON PARAMETER ivorysql.dbtimezone TO ;` +- **All roles in the same database see the same value**: `DBTIMEZONE` is a pure database property and does not support "different roles seeing different values within the same database" — any `ALTER ROLE ... SET` attempting to differentiate by role is rejected directly at the command level (except `ALTER ROLE ... RESET`, which is used to clean up stale legacy records) +- **Value format validation**: Accepts a UTC offset in the form `[+-]HH:MI`, in the range `-12:59` to `+14:00` (consistent with Oracle), or a valid IANA time zone region name + +== Syntax Examples + +=== Basic Usage + +[source,sql] +---- +SELECT dbtimezone() FROM dual; +-- dbtimezone +-- ------------ +-- +00:00 +-- (1 row) +---- + +=== Comparison with SESSIONTIMEZONE: Unaffected by the Session Time Zone + +[source,sql] +---- +SET timezone = 'Asia/Hong_Kong'; +SELECT sessiontimezone() FROM dual; +-- sessiontimezone +-- ----------------- +-- Asia/Hong_Kong + +SELECT dbtimezone() FROM dual; +-- Still the fixed database value, unaffected by the SET timezone above +-- dbtimezone +-- ------------ +-- +00:00 +---- + +=== Fixing the Time Zone of a Database via ALTER DATABASE + +[source,sql] +---- +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+08:00'; +-- Takes effect only after disconnecting and reconnecting (a new session) +\c mydb +SELECT dbtimezone() FROM dual; -- +08:00 +---- + +=== Granting a Non-Superuser Permission to Manage DBTIMEZONE for Their Own Database + +[source,sql] +---- +-- The superuser performs a one-time grant: +GRANT SET ON PARAMETER ivorysql.dbtimezone TO app_owner; + +-- app_owner can then set it for a database they own: +\c app_db app_owner +ALTER DATABASE app_db SET ivorysql.dbtimezone = '+05:30'; +\c app_db app_owner +SELECT dbtimezone() FROM dual; -- +05:30 +---- + +== Error Handling + +=== Direct SET Within a Session Is Rejected + +[source,sql] +---- +SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +---- + +=== ALTER ROLE ... SET Is Rejected Directly at the Command Level + +[source,sql] +---- +ALTER ROLE myrole IN DATABASE mydb SET ivorysql.dbtimezone = '+09:00'; +-- ERROR: parameter "ivorysql.dbtimezone" cannot be set +-- DETAIL: "ivorysql.dbtimezone" can only be set with ALTER DATABASE ... SET, not within a session or per-role. +-- HINT: Use ALTER DATABASE ... SET ivorysql.dbtimezone instead, or ALTER ROLE ... RESET ivorysql.dbtimezone to remove a stale per-role override. + +ALTER ROLE myrole SET ivorysql.dbtimezone = '+09:00'; -- Also errors +ALTER ROLE ALL SET ivorysql.dbtimezone = '+09:00'; -- Also errors + +-- RESET / RESET ALL are unaffected and can still be used to clean up stale legacy records: +ALTER ROLE myrole IN DATABASE mydb RESET ivorysql.dbtimezone; -- OK +---- + +=== Invalid Value / Out-of-Range Offset + +[source,sql] +---- +ALTER DATABASE mydb SET ivorysql.dbtimezone = 'not_a_zone'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "not_a_zone" +-- DETAIL: "not_a_zone" is not a valid UTC offset (+/-HH:MI) or time zone name. + +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+15:00'; +-- ERROR: invalid value for parameter "ivorysql.dbtimezone": "+15:00" +-- DETAIL: time zone offset "+15:00" is out of range for DBTIMEZONE (-12:59 to +14:00) +---- + +=== An Unauthorized Ordinary User Executing ALTER DATABASE + +[source,sql] +---- +-- normal_user is the owner of mydb, but has not been granted permission via GRANT SET ON PARAMETER +\c mydb normal_user +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+08:00'; +-- ERROR: permission denied to set parameter "ivorysql.dbtimezone" +---- + +NOTE: Owning or creating a database does not imply permission to set every GUC parameter for that database — database-level permission (whether you can `ALTER` this database) and GUC-parameter-level permission (whether you can "set" this parameter) are two independent checks. + +== Cleanup + +[source,sql] +---- +-- Restore the default value: +ALTER DATABASE mydb SET ivorysql.dbtimezone = '+00:00'; +-- Or remove the database's custom setting entirely, falling back to the cluster default: +ALTER DATABASE mydb RESET ivorysql.dbtimezone; +---- From 7f7238ec804fbc81c98d445e7995a7d256a364ff Mon Sep 17 00:00:00 2001 From: Steven Niu Date: Wed, 12 Aug 2026 01:58:31 +0000 Subject: [PATCH 2/2] move the dbtimezone doc into built-in function section --- CN/modules/ROOT/nav.adoc | 2 +- .../dbtimezone_impl.adoc | 0 EN/modules/ROOT/nav.adoc | 2 +- .../dbtimezone_impl_en.adoc | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename CN/modules/ROOT/pages/master/{compatibility_features_design => oracle_builtin_functions}/dbtimezone_impl.adoc (100%) rename EN/modules/ROOT/pages/master/{compatibility_features_design => oracle_builtin_functions}/dbtimezone_impl_en.adoc (100%) diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index bb4fb7e3..b62fe4a5 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -110,12 +110,12 @@ **** xref:master/compatibility_features_design/with_function_procedure_impl.adoc[WITH FUNCTION/PROCEDURE] **** xref:master/compatibility_features_design/create_index_online.adoc[索引 ONLINE 参数] **** xref:master/compatibility_features_design/alter_index_unusable_impl.adoc[禁用索引] -**** xref:master/compatibility_features_design/dbtimezone_impl.adoc[dbtimezone] *** 内置函数 **** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context] **** xref:master/oracle_builtin_functions/userenv.adoc[userenv] **** xref:master/oracle_builtin_functions/rawtohex.adoc[rawtohex] **** xref:master/oracle_builtin_functions/stragg.adoc[stragg] +**** xref:master/oracle_builtin_functions/dbtimezone_impl.adoc[dbtimezone] *** xref:master/gb18030.adoc[国标GB18030] * 参考指南 ** xref:master/tools_reference.adoc[工具参考] diff --git a/CN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl.adoc b/CN/modules/ROOT/pages/master/oracle_builtin_functions/dbtimezone_impl.adoc similarity index 100% rename from CN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl.adoc rename to CN/modules/ROOT/pages/master/oracle_builtin_functions/dbtimezone_impl.adoc diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index 1aa096ee..e4221c78 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -110,12 +110,12 @@ *** xref:master/compatibility_features_design/with_function_procedure_impl_en.adoc[WITH FUNCTION/PROCEDURE] *** xref:master/compatibility_features_design/create_index_online.adoc[ONLINE Parameter for CREATE INDEX] *** xref:master/compatibility_features_design/alter_index_unusable_impl_en.adoc[Alter Index Unusable] -*** xref:master/compatibility_features_design/dbtimezone_impl_en.adoc[dbtimezone] ** Built-in Functions *** xref:master/oracle_builtin_functions/sys_context.adoc[sys_context] *** xref:master/oracle_builtin_functions/userenv.adoc[userenv] *** xref:master/oracle_builtin_functions/rawtohex.adoc[rawtohex] *** xref:master/oracle_builtin_functions/stragg.adoc[stragg] +*** xref:master/oracle_builtin_functions/dbtimezone_impl_en.adoc[dbtimezone] ** xref:master/gb18030.adoc[GB18030 Character Set] * Reference ** xref:master/tools_reference.adoc[Tool Reference] diff --git a/EN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl_en.adoc b/EN/modules/ROOT/pages/master/oracle_builtin_functions/dbtimezone_impl_en.adoc similarity index 100% rename from EN/modules/ROOT/pages/master/compatibility_features_design/dbtimezone_impl_en.adoc rename to EN/modules/ROOT/pages/master/oracle_builtin_functions/dbtimezone_impl_en.adoc