diff --git a/CN/modules/ROOT/nav.adoc b/CN/modules/ROOT/nav.adoc index b62fe4a5..ce0d05d5 100644 --- a/CN/modules/ROOT/nav.adoc +++ b/CN/modules/ROOT/nav.adoc @@ -1,127 +1,129 @@ -* xref:master/about_ivorysql.adoc[关于IvorySQL] -** xref:master/welcome.adoc[欢迎] -** xref:master/release_notes.adoc[发行说明] -* 快速上手 -** xref:master/getting-started/quick_start.adoc[快速开始] -* 安装部署 -** xref:master/installation_guide.adoc[安装指南] -** xref:master/cluster_setup.adoc[集群搭建] -* Oracle兼容功能 -** xref:master/oracle_compatibility/compat_case_conversion.adoc[1、大小写转换] -** xref:master/oracle_compatibility/compat_like_operator.adoc[2、LIKE操作符] -** xref:master/oracle_compatibility/anonymous_block.adoc[3、匿名块] -** xref:master/oracle_compatibility/compat_function_procedure.adoc[4、函数与存储过程] -** xref:master/oracle_compatibility/builtin_types_functions.adoc[5、内置数据类型与内置函数] -** xref:master/oracle_compatibility/port_ip.adoc[6、端口与IP] -** xref:master/oracle_compatibility/xml_functions.adoc[7、XML函数] -** xref:master/oracle_compatibility/sequence.adoc[8、sequence] -** xref:master/oracle_compatibility/package.adoc[9、包] -** xref:master/oracle_compatibility/invisible_column.adoc[10、不可见列] -** xref:master/oracle_compatibility/compat_rowid.adoc[11、RowID] -** xref:master/oracle_compatibility/compat_out_parameter.adoc[12、OUT 参数] -** xref:master/oracle_compatibility/compat_type_rowtype.adoc[13、%TYPE、%ROWTYPE] -** xref:master/oracle_compatibility/compat_nls_parameter.adoc[14、NLS 参数] -** xref:master/oracle_compatibility/compat_force_view.adoc[15、Force View] -** xref:master/oracle_compatibility/compat_nested_function.adoc[16、嵌套子函数] -** xref:master/oracle_compatibility/compat_sys_guid.adoc[17、sys_guid 函数] -** xref:master/oracle_compatibility/compat_empty_string_to_null.adoc[18、空字符串转null] -** xref:master/oracle_compatibility/compat_call_into.adoc[19、CALL INTO] -** xref:master/oracle_compatibility/compat_read_only_view.adoc[20、视图只读] -** xref:master/oracle_compatibility/with_function_procedure.adoc[21、WITH FUNCTION/PROCEDURE] -** 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部署] -*** xref:master/containerization/operator_deployment.adoc[Operator部署] -*** xref:master/containerization/docker_podman_deployment.adoc[Docker & Podman部署] -*** xref:master/containerization/docker_swarm_compose_deployment.adoc[Docker Swarm & Docker Compose部署] -** 云服务平台指南 -*** xref:master/cloud_platform/ivorysql_cloud_installation.adoc[IvorySQL Cloud安装] -*** xref:master/cloud_platform/ivorysql_cloud_usage.adoc[IvorySQL Cloud使用] -* IvorySQL生态 -** xref:master/cpu_os_adaptation/cpu_architecture_adaptation.adoc[芯片架构适配] -** xref:master/cpu_os_adaptation/os_architecture_adaptation.adoc[操作系统适配] -** 生态组件适配 -*** xref:master/ecosystem_components/ecosystem_overview.adoc[概述] -*** xref:master/ecosystem_components/postgis.adoc[postgis] -*** xref:master/ecosystem_components/pgvector.adoc[pgvector] -*** xref:master/ecosystem_components/pgddl.adoc[pgddl(DDL Extractor)] -*** xref:master/ecosystem_components/pg_cron.adoc[pg_cron] -*** xref:master/ecosystem_components/pgsql_http.adoc[pgsql-http] -*** xref:master/ecosystem_components/plpgsql_check.adoc[plpgsql_check] -*** xref:master/ecosystem_components/pgroonga.adoc[pgroonga] -*** xref:master/ecosystem_components/pgaudit.adoc[pgaudit] -*** xref:master/ecosystem_components/pgrouting.adoc[pgrouting] -*** xref:master/ecosystem_components/system_stats.adoc[system_stats] -*** xref:master/ecosystem_components/wal2json.adoc[wal2json] -*** xref:master/ecosystem_components/pg_stat_monitor.adoc[pg_stat_monitor] -*** xref:master/ecosystem_components/pg_ai_query.adoc[pg_ai_query] -*** xref:master/ecosystem_components/pg_partman.adoc[pg_partman] -*** xref:master/ecosystem_components/pgbouncer.adoc[pgbouncer] -*** xref:master/ecosystem_components/age.adoc[age] -*** xref:master/ecosystem_components/pg_curl.adoc[pg_curl] -*** xref:master/ecosystem_components/pg_textsearch.adoc[pg_textsearch] -*** xref:master/ecosystem_components/pg_hint_plan.adoc[pg_hint_plan] -*** xref:master/ecosystem_components/redis_fdw.adoc[redis_fdw] -*** xref:master/ecosystem_components/pg_show_plans.adoc[pg_show_plans] -*** xref:master/ecosystem_components/pg_bulkload.adoc[pg_bulkload] -*** xref:master/ecosystem_components/pg_bigm.adoc[pg_bigm] -*** xref:master/ecosystem_components/pg_profile.adoc[pg_profile] -*** xref:master/ecosystem_components/pg_repack.adoc[pg_repack] -*** xref:master/ecosystem_components/pgdog.adoc[PgDog] -*** xref:master/ecosystem_components/pg_readonly.adoc[pg_readonly] -*** xref:master/ecosystem_components/zhparser.adoc[zhparser] -*** xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] -*** xref:master/ecosystem_components/set_user.adoc[set_user] -* 监控运维 -** xref:master/getting-started/daily_monitoring.adoc[日常监控] -** xref:master/getting-started/daily_maintenance.adoc[日常维护] -** xref:master/operation_guide.adoc[运维管理指南] -* 数据迁移 -** xref:master/migration_guide.adoc[迁移指南] -* IvorySQL开发者 -** xref:master/contribution/contribution_guide.adoc[贡献指南] -** xref:master/developer_guide.adoc[开发者指南] -** IvorySQL架构设计 -*** 查询处理 -**** xref:master/architecture/dual_parser.adoc[双parser] -*** 兼容框架 -**** xref:master/architecture/framework_design.adoc[框架设计] -**** xref:master/architecture/guc_framework.adoc[GUC框架] -**** xref:master/architecture/dual_mode_design.adoc[双模式设计] -**** xref:master/architecture/initdb_process.adoc[initdb过程] -*** 兼容特性 -**** xref:master/compatibility_features_design/like_operator.adoc[like] -**** xref:master/compatibility_features_design/rowid.adoc[RowID] -**** xref:master/compatibility_features_design/out_parameter.adoc[OUT 参数] -**** xref:master/compatibility_features_design/type_rowtype.adoc[%TYPE、%ROWTYPE] -**** xref:master/compatibility_features_design/nls_parameter.adoc[NLS 参数] -**** xref:master/compatibility_features_design/function_procedure.adoc[函数与存储过程] -**** xref:master/compatibility_features_design/nested_function.adoc[嵌套子函数] -**** xref:master/compatibility_features_design/force_view.adoc[Force View] -**** xref:master/compatibility_features_design/case_conversion.adoc[大小写转换] -**** xref:master/compatibility_features_design/sys_guid_function.adoc[sys_guid 函数] -**** xref:master/compatibility_features_design/empty_string_to_null.adoc[空字符串转null] -**** xref:master/compatibility_features_design/call_into.adoc[CALL INTO] -**** xref:master/compatibility_features_design/read_only_view.adoc[视图只读] -**** 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/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[工具参考] -** xref:master/contribution/asciidoc_syntax_reference.adoc[asciidoc语法快速参考] -** xref:master/pg_reference/pg_parameters_reference.adoc[PG参数参考手册] -** xref:master/pg_reference/pg_functions_reference.adoc[PG函数参考手册] -* 常见问题解答 -** xref:master/contribution/faq.adoc[FAQ] - +* xref:master/about_ivorysql.adoc[关于IvorySQL] +** xref:master/welcome.adoc[欢迎] +** xref:master/release_notes.adoc[发行说明] +* 快速上手 +** xref:master/getting-started/quick_start.adoc[快速开始] +* 安装部署 +** xref:master/installation_guide.adoc[安装指南] +** xref:master/cluster_setup.adoc[集群搭建] +* Oracle兼容功能 +** xref:master/oracle_compatibility/compat_case_conversion.adoc[1、大小写转换] +** xref:master/oracle_compatibility/compat_like_operator.adoc[2、LIKE操作符] +** xref:master/oracle_compatibility/anonymous_block.adoc[3、匿名块] +** xref:master/oracle_compatibility/compat_function_procedure.adoc[4、函数与存储过程] +** xref:master/oracle_compatibility/builtin_types_functions.adoc[5、内置数据类型与内置函数] +** xref:master/oracle_compatibility/port_ip.adoc[6、端口与IP] +** xref:master/oracle_compatibility/xml_functions.adoc[7、XML函数] +** xref:master/oracle_compatibility/sequence.adoc[8、sequence] +** xref:master/oracle_compatibility/package.adoc[9、包] +** xref:master/oracle_compatibility/invisible_column.adoc[10、不可见列] +** xref:master/oracle_compatibility/compat_rowid.adoc[11、RowID] +** xref:master/oracle_compatibility/compat_out_parameter.adoc[12、OUT 参数] +** xref:master/oracle_compatibility/compat_type_rowtype.adoc[13、%TYPE、%ROWTYPE] +** xref:master/oracle_compatibility/compat_nls_parameter.adoc[14、NLS 参数] +** xref:master/oracle_compatibility/compat_force_view.adoc[15、Force View] +** xref:master/oracle_compatibility/compat_nested_function.adoc[16、嵌套子函数] +** xref:master/oracle_compatibility/compat_sys_guid.adoc[17、sys_guid 函数] +** xref:master/oracle_compatibility/compat_empty_string_to_null.adoc[18、空字符串转null] +** xref:master/oracle_compatibility/compat_call_into.adoc[19、CALL INTO] +** xref:master/oracle_compatibility/compat_read_only_view.adoc[20、视图只读] +** xref:master/oracle_compatibility/with_function_procedure.adoc[21、WITH FUNCTION/PROCEDURE] +** 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/oracle_compatibility/compat_lnnvl.adoc[26、LNNVL 函数] +* 容器化与云服务 +** 容器化指南 +*** xref:master/containerization/k8s_deployment.adoc[K8S部署] +*** xref:master/containerization/operator_deployment.adoc[Operator部署] +*** xref:master/containerization/docker_podman_deployment.adoc[Docker & Podman部署] +*** xref:master/containerization/docker_swarm_compose_deployment.adoc[Docker Swarm & Docker Compose部署] +** 云服务平台指南 +*** xref:master/cloud_platform/ivorysql_cloud_installation.adoc[IvorySQL Cloud安装] +*** xref:master/cloud_platform/ivorysql_cloud_usage.adoc[IvorySQL Cloud使用] +* IvorySQL生态 +** xref:master/cpu_os_adaptation/cpu_architecture_adaptation.adoc[芯片架构适配] +** xref:master/cpu_os_adaptation/os_architecture_adaptation.adoc[操作系统适配] +** 生态组件适配 +*** xref:master/ecosystem_components/ecosystem_overview.adoc[概述] +*** xref:master/ecosystem_components/postgis.adoc[postgis] +*** xref:master/ecosystem_components/pgvector.adoc[pgvector] +*** xref:master/ecosystem_components/pgddl.adoc[pgddl(DDL Extractor)] +*** xref:master/ecosystem_components/pg_cron.adoc[pg_cron] +*** xref:master/ecosystem_components/pgsql_http.adoc[pgsql-http] +*** xref:master/ecosystem_components/plpgsql_check.adoc[plpgsql_check] +*** xref:master/ecosystem_components/pgroonga.adoc[pgroonga] +*** xref:master/ecosystem_components/pgaudit.adoc[pgaudit] +*** xref:master/ecosystem_components/pgrouting.adoc[pgrouting] +*** xref:master/ecosystem_components/system_stats.adoc[system_stats] +*** xref:master/ecosystem_components/wal2json.adoc[wal2json] +*** xref:master/ecosystem_components/pg_stat_monitor.adoc[pg_stat_monitor] +*** xref:master/ecosystem_components/pg_ai_query.adoc[pg_ai_query] +*** xref:master/ecosystem_components/pg_partman.adoc[pg_partman] +*** xref:master/ecosystem_components/pgbouncer.adoc[pgbouncer] +*** xref:master/ecosystem_components/age.adoc[age] +*** xref:master/ecosystem_components/pg_curl.adoc[pg_curl] +*** xref:master/ecosystem_components/pg_textsearch.adoc[pg_textsearch] +*** xref:master/ecosystem_components/pg_hint_plan.adoc[pg_hint_plan] +*** xref:master/ecosystem_components/redis_fdw.adoc[redis_fdw] +*** xref:master/ecosystem_components/pg_show_plans.adoc[pg_show_plans] +*** xref:master/ecosystem_components/pg_bulkload.adoc[pg_bulkload] +*** xref:master/ecosystem_components/pg_bigm.adoc[pg_bigm] +*** xref:master/ecosystem_components/pg_profile.adoc[pg_profile] +*** xref:master/ecosystem_components/pg_repack.adoc[pg_repack] +*** xref:master/ecosystem_components/pgdog.adoc[PgDog] +*** xref:master/ecosystem_components/pg_readonly.adoc[pg_readonly] +*** xref:master/ecosystem_components/zhparser.adoc[zhparser] +*** xref:master/ecosystem_components/pgbackrest.adoc[pgBackRest] +*** xref:master/ecosystem_components/set_user.adoc[set_user] +* 监控运维 +** xref:master/getting-started/daily_monitoring.adoc[日常监控] +** xref:master/getting-started/daily_maintenance.adoc[日常维护] +** xref:master/operation_guide.adoc[运维管理指南] +* 数据迁移 +** xref:master/migration_guide.adoc[迁移指南] +* IvorySQL开发者 +** xref:master/contribution/contribution_guide.adoc[贡献指南] +** xref:master/developer_guide.adoc[开发者指南] +** IvorySQL架构设计 +*** 查询处理 +**** xref:master/architecture/dual_parser.adoc[双parser] +*** 兼容框架 +**** xref:master/architecture/framework_design.adoc[框架设计] +**** xref:master/architecture/guc_framework.adoc[GUC框架] +**** xref:master/architecture/dual_mode_design.adoc[双模式设计] +**** xref:master/architecture/initdb_process.adoc[initdb过程] +*** 兼容特性 +**** xref:master/compatibility_features_design/like_operator.adoc[like] +**** xref:master/compatibility_features_design/rowid.adoc[RowID] +**** xref:master/compatibility_features_design/out_parameter.adoc[OUT 参数] +**** xref:master/compatibility_features_design/type_rowtype.adoc[%TYPE、%ROWTYPE] +**** xref:master/compatibility_features_design/nls_parameter.adoc[NLS 参数] +**** xref:master/compatibility_features_design/function_procedure.adoc[函数与存储过程] +**** xref:master/compatibility_features_design/nested_function.adoc[嵌套子函数] +**** xref:master/compatibility_features_design/force_view.adoc[Force View] +**** xref:master/compatibility_features_design/case_conversion.adoc[大小写转换] +**** xref:master/compatibility_features_design/sys_guid_function.adoc[sys_guid 函数] +**** xref:master/compatibility_features_design/empty_string_to_null.adoc[空字符串转null] +**** xref:master/compatibility_features_design/call_into.adoc[CALL INTO] +**** xref:master/compatibility_features_design/read_only_view.adoc[视图只读] +**** 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/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/oracle_builtin_functions/lnnvl.adoc[lnnvl] +*** xref:master/gb18030.adoc[国标GB18030] +* 参考指南 +** xref:master/tools_reference.adoc[工具参考] +** xref:master/contribution/asciidoc_syntax_reference.adoc[asciidoc语法快速参考] +** xref:master/pg_reference/pg_parameters_reference.adoc[PG参数参考手册] +** xref:master/pg_reference/pg_functions_reference.adoc[PG函数参考手册] +* 常见问题解答 +** xref:master/contribution/faq.adoc[FAQ] + diff --git a/CN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc b/CN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc new file mode 100644 index 00000000..58c51111 --- /dev/null +++ b/CN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc @@ -0,0 +1,131 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += LNNVL 函数的实现 + +== 目的 + +IvorySQL 在 `contrib/ivorysql_ora` 扩展中新增 `sys.lnnvl(boolean)` 函数, +实现与 Oracle 同名函数一致的条件取反行为:条件求值为 FALSE 或 UNKNOWN 时返回 TRUE, +求值为 TRUE 时返回 FALSE。 + +== 实现说明 + +=== 语义等价于 IS NOT TRUE + +LNNVL 的真值表与 SQL 标准的 `IS NOT TRUE` 谓词逐格相同: + +[cols="1,1,1"] +|=== +|条件求值结果 |`LNNVL(cond)` |`cond IS NOT TRUE` + +|TRUE +|FALSE +|FALSE + +|FALSE +|TRUE +|TRUE + +|UNKNOWN +|TRUE +|TRUE +|=== + +因此实现无需新增任何求值逻辑,函数体只是这一个谓词。 +PostgreSQL 中 `IS NOT TRUE` 的求值位于 +`src/backend/executor/execExprInterp.c` 的 `EEOP_BOOLTEST_IS_NOT_TRUE` 分支, +入参为 NULL 时直接返回 true 并清除结果的 null 标记, +因此 LNNVL 自身永不返回 NULL。 + +=== SQL 定义 + +函数定义位于 +`contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql`。 + +[source,sql] +---- +CREATE FUNCTION sys.lnnvl(pg_catalog.bool) +RETURNS pg_catalog.bool +AS $$SELECT $1 IS NOT TRUE$$ +LANGUAGE sql +CALLED ON NULL INPUT +PARALLEL SAFE +IMMUTABLE; +---- + +各项属性的选择理由: + +[cols="1,1,3"] +|=== +|属性 |取值 |理由 + +|所在模式 +|`sys` +|与其它 Oracle 兼容函数一致。`sys` 仅在 Oracle 兼容模式下进入隐式 `search_path`。 + +|参数类型 +|`pg_catalog.bool` +|显式限定模式,避免 Oracle 模式下 `sys` 位于 `search_path` 首位时类型名解析受影响。 + +|语言 +|`sql` +|可被 `inline_function()` 内联,见下节。C 函数不会被内联。 + +|NULL 处理 +|`CALLED ON NULL INPUT` +|必须项,见下节。 + +|易变性 +|`IMMUTABLE` +|纯逻辑运算,同一输入恒定输出;也是用于表达式索引等场景的前提。 + +|并行性 +|`PARALLEL SAFE` +|无副作用。若不声明,默认为 `PARALLEL UNSAFE`,会禁用相关查询的并行计划。 +|=== + +=== CALLED ON NULL INPUT 是必须项 + +`CALLED ON NULL INPUT` 是 SQL 的默认值,此处显式写出是为了防止被误改。 + +若声明为 `STRICT`,入参为 NULL 时函数体根本不会执行,直接返回 NULL, +而正确行为是返回 TRUE —— 恰好毁掉 LNNVL 存在的唯一理由,且不产生任何错误。 +风险来自代码风格的一致性压力:`builtin_functions--1.0.sql` 中多数邻近函数均声明为 +`STRICT`,按邻居样式统一即会踩中。 + +`STRICT` 还会连带废掉内联。声明为严格意味着承诺"NULL 进则 NULL 出", +而 `IS NOT TRUE` 本身并非严格谓词,优化器无法在保持语义的前提下将其展开, +只能保留函数调用: + +[source,text] +---- +-- CALLED ON NULL INPUT +Filter: (t.v IS NOT TRUE) + +-- STRICT +Filter: f_strict(t.v) +---- + +因此误写为 `STRICT` 是双重损失:语义错误且静默,同时性能退化为逐行函数调用。 + +=== 函数内联 + +函数体是可内联的简单 SQL 表达式,优化器的 `inline_function()` 会将其折叠到调用处: + +[source,sql] +---- +EXPLAIN (VERBOSE, COSTS OFF) SELECT id FROM t WHERE LNNVL(v >= 0.2); +---- + +[source,text] +---- + Seq Scan on public.t + Output: id + Filter: ((t.v >= 0.2) IS NOT TRUE) +---- + +计划中已不存在函数调用,与用户直接书写 `IS NOT TRUE` 的计划逐字相同, +因此不引入额外运行期开销。 diff --git a/CN/modules/ROOT/pages/master/oracle_compatibility/compat_lnnvl.adoc b/CN/modules/ROOT/pages/master/oracle_compatibility/compat_lnnvl.adoc new file mode 100644 index 00000000..88a815ba --- /dev/null +++ b/CN/modules/ROOT/pages/master/oracle_compatibility/compat_lnnvl.adoc @@ -0,0 +1,253 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += LNNVL 函数 + +== 目的 + +本文档说明 IvorySQL 中 `sys.lnnvl` 函数的功能,该函数以兼容 Oracle 的方式 +对条件取反,并且在条件求值结果为 UNKNOWN 时仍然返回 TRUE。 + +== 功能说明 + +* `sys.lnnvl(boolean)` 接受一个条件,条件求值为 FALSE 或 UNKNOWN 时返回 TRUE, + 求值为 TRUE 时返回 FALSE。 +* 函数**永不返回 NULL**,返回值必为 TRUE 或 FALSE 之一, + 因此可以直接用作 WHERE 条件而不会引入新的 UNKNOWN。 +* 主要用途是消除三值逻辑造成的"行丢失":直接书写 `NOT` 时, + 条件为 UNKNOWN 的行在原条件和取反条件下都会被丢弃; + 而条件与其 LNNVL 构成对整表的严格划分,两者的结果行数之和恒等于总行数。 + +真值表: + +[cols="1,1"] +|=== +|条件求值结果 |返回值 + +|TRUE +|FALSE + +|FALSE +|TRUE + +|UNKNOWN(条件中含 NULL) +|TRUE +|=== + +== 语法 + +[source,sql] +---- +sys.lnnvl(condition) +---- + +[cols="1,3"] +|=== +|参数 |说明 + +|`condition` +|任意布尔表达式。不接受非布尔类型,且不存在从整型的隐式转换。 +|=== + +返回类型:`boolean` + +== 测试用例 + +=== 测试环境准备 + +[source,sql] +---- +CREATE TABLE lnnvl_test (a INT, b INT); +INSERT INTO lnnvl_test VALUES (2, NULL); +---- + +=== 真值表验证 + +[source,sql] +---- +-- a = 2,b 为 NULL +SELECT LNNVL(a = 1) FROM lnnvl_test; -- 期望:t(条件为 FALSE) +SELECT LNNVL(a = 2) FROM lnnvl_test; -- 期望:f(条件为 TRUE) +SELECT LNNVL(a IS NULL) FROM lnnvl_test; -- 期望:t(条件为 FALSE) +SELECT LNNVL(b = 1) FROM lnnvl_test; -- 期望:t(条件为 UNKNOWN) +SELECT LNNVL(b IS NULL) FROM lnnvl_test; -- 期望:f(条件为 TRUE) +SELECT LNNVL(a = b) FROM lnnvl_test; -- 期望:t(条件为 UNKNOWN) +---- + +=== NULL 入参 + +[source,sql] +---- +-- NULL 入参必须返回 t,这是 LNNVL 的核心行为 +SELECT LNNVL(NULL); -- 期望:t +SELECT LNNVL(NULL::boolean); -- 期望:t +---- + +=== 划分性质 + +[source,sql] +---- +DROP TABLE lnnvl_test; +CREATE TABLE lnnvl_test (id INT, v NUMERIC); +INSERT INTO lnnvl_test VALUES (1,0.30),(2,0.10),(3,NULL),(4,0.25),(5,NULL); + +SELECT count(*) FROM lnnvl_test WHERE v >= 0.2; -- 期望:2 +SELECT count(*) FROM lnnvl_test WHERE LNNVL(v >= 0.2); -- 期望:3 +SELECT count(*) FROM lnnvl_test; -- 期望:5 + +-- 2 + 3 = 5,条件与其 LNNVL 覆盖全表,无行丢失 +-- 若改用 NOT,两者之和会小于总行数 +SELECT count(*) FROM lnnvl_test WHERE NOT (v >= 0.2); -- 期望:2,合计仅 4 +---- + +=== NOT IN 遇含 NULL 的子查询 + +[source,sql] +---- +CREATE TABLE lnnvl_bl (d INT); +INSERT INTO lnnvl_bl VALUES (1), (NULL); + +-- 黑名单含 NULL 时,NOT IN 返回空集 +SELECT count(*) FROM lnnvl_test WHERE id NOT IN (SELECT d FROM lnnvl_bl); +-- 期望:0 + +-- 改写为 LNNVL(... IN ...) 后得到符合直觉的结果 +SELECT count(*) FROM lnnvl_test WHERE LNNVL(id IN (SELECT d FROM lnnvl_bl)); +-- 期望:4 +---- + +`id NOT IN (1, NULL)` 展开为 `id <> 1 AND id <> NULL`, +其中 `id <> NULL` 恒为 UNKNOWN,导致整个条件不为 TRUE,因此所有行都被丢弃。 +`LNNVL(id IN (...))` 把 UNKNOWN 判定为"非真"从而保留这些行。 + +=== 参数类型检查 + +[source,sql] +---- +-- 参数必须是布尔类型,不存在从整型的隐式转换 +SELECT LNNVL(1); +-- 期望报错:function lnnvl(pg_catalog.int4) does not exist +---- + +=== 测试环境清理 + +[source,sql] +---- +DROP TABLE lnnvl_bl; +DROP TABLE lnnvl_test; +---- + +== 与 Oracle 的行为差异 + +Oracle 的语法只接受单个简单条件。IvorySQL 以普通函数实现, +参数在重载决议时已经是一个布尔表达式,产生它的 `AND`、`OR`、`NOT`、`BETWEEN` +对函数不可见,因此无法复现 Oracle 的形态限制,**接受的形态是 Oracle 的超集**。 + +方向性结论:能在 Oracle 上运行的 LNNVL 写法在 IvorySQL 上一定能运行; +反之不保证 —— 在 IvorySQL 上写通的复合条件形式搬回 Oracle 会报错。 + +[cols="2,1,1"] +|=== +|参数形态 |Oracle |IvorySQL + +|`LNNVL(1 = 1 AND 2 = 2)` +|拒绝 +|接受,返回 f + +|`LNNVL(1 = 1 OR 2 = 2)` +|拒绝 +|接受,返回 f + +|`LNNVL(1 BETWEEN 0 AND 5)` +|拒绝 +|接受,返回 f + +|`LNNVL(1 NOT BETWEEN 0 AND 5)` +|拒绝 +|接受,返回 t + +|`LNNVL(NOT (1 = 1))` +|拒绝(ORA-00936) +|接受,返回 t + +|`LNNVL(NOT EXISTS (SELECT ...))` +|拒绝(ORA-00936) +|接受,返回 t + +|`LNNVL(1 IN (1,2))` +|拒绝(ORA-13207) +|接受,返回 f + +|`LNNVL(1 NOT IN (1,2))` +|拒绝(ORA-13207) +|接受,返回 t +|=== + +其它差异: + +[cols="1,2,2"] +|=== +|场景 |Oracle |IvorySQL + +|错误码与错误文本 +|`ORA-` 编号 +|PostgreSQL 的 SQLSTATE 与英文描述 + +|`LNNVL(1)` +|依据其 BOOLEAN 类型转换规则处理 +|报函数不存在,不做隐式转换 +|=== + +需要表达复合条件时,按 Oracle 的做法拆成多个 LNNVL 调用再用 `AND` 或 `OR` 连接: + +[source,sql] +---- +SELECT LNNVL(1 = 1) OR LNNVL(2 = 3); -- 期望:f +SELECT LNNVL(1 = 1) AND LNNVL(2 = 3); -- 期望:f +SELECT NOT LNNVL(1 = 1); -- 期望:t +---- + +注意拆分时需遵循德摩根律:`LNNVL(A AND B)` 语义上对应 +`LNNVL(A) OR LNNVL(B)`,写成 `AND` 会改变结果。 + +== 与 NOT 的比较 + +[cols="1,2,2"] +|=== +|条件求值结果 |`NOT cond` |`LNNVL(cond)` + +|TRUE +|FALSE +|FALSE + +|FALSE +|TRUE +|TRUE + +|UNKNOWN +|UNKNOWN(行被丢弃) +|TRUE(行被保留) +|=== + +两者对"不确定"的处理方向相反:`NOT` 丢弃,LNNVL 保留。 +严格而言,`NOT` 在三值逻辑下的行为是正确的;LNNVL 做的是把 +"未知视为不满足原条件"这一假设显式化,业务上通常这才是所需的结果。 + +== 相关等价写法 + +`LNNVL(cond)` 等价于 SQL 标准谓词 `cond IS NOT TRUE`。在不要求 Oracle +兼容写法的场合可直接使用后者,两者的执行计划完全相同。 + +对于 `NOT IN` 遇 NULL 的场景,PostgreSQL 的惯用解法是 `NOT EXISTS`: + +[source,sql] +---- +SELECT count(*) FROM lnnvl_test t + WHERE NOT EXISTS (SELECT 1 FROM lnnvl_bl b WHERE b.d = t.id); +---- + +该写法与 `LNNVL(id IN (...))` 结果相同且更可移植; +LNNVL 的优势在于改写成本 —— 它是就地包一层, +而 `NOT EXISTS` 需要把子查询重构成相关子查询。 diff --git a/EN/modules/ROOT/nav.adoc b/EN/modules/ROOT/nav.adoc index e4221c78..e117747f 100644 --- a/EN/modules/ROOT/nav.adoc +++ b/EN/modules/ROOT/nav.adoc @@ -31,7 +31,8 @@ ** 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] +** xref:master/oracle_compatibility/compat_dbtimezone_en.adoc[25、dbtimezone] +** xref:master/oracle_compatibility/compat_lnnvl.adoc[26、LNNVL Function] * Containerization and Cloud Service ** Containerization *** xref:master/containerization/k8s_deployment.adoc[K8S deployment] @@ -116,6 +117,7 @@ *** 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/oracle_builtin_functions/lnnvl.adoc[lnnvl] ** xref:master/gb18030.adoc[GB18030 Character Set] * Reference ** xref:master/tools_reference.adoc[Tool Reference] diff --git a/EN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc b/EN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc new file mode 100644 index 00000000..a7fb3bd3 --- /dev/null +++ b/EN/modules/ROOT/pages/master/oracle_builtin_functions/lnnvl.adoc @@ -0,0 +1,145 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += Implementation of the LNNVL Function + +== Purpose + +IvorySQL adds the `sys.lnnvl(boolean)` function in the `contrib/ivorysql_ora` +extension, reproducing the behaviour of the Oracle function of the same name: +it returns TRUE when the condition evaluates to FALSE or UNKNOWN, and FALSE +when the condition evaluates to TRUE. + +== Implementation Notes + +=== Semantically Equivalent to IS NOT TRUE + +The truth table of LNNVL is cell-for-cell that of the SQL standard +`IS NOT TRUE` predicate: + +[cols="1,1,1"] +|=== +|Condition evaluates to |`LNNVL(cond)` |`cond IS NOT TRUE` + +|TRUE +|FALSE +|FALSE + +|FALSE +|TRUE +|TRUE + +|UNKNOWN +|TRUE +|TRUE +|=== + +No new evaluation logic is therefore required; the function body is just that +predicate. In PostgreSQL, `IS NOT TRUE` is evaluated by the +`EEOP_BOOLTEST_IS_NOT_TRUE` case in +`src/backend/executor/execExprInterp.c`, which returns true and clears the +result null flag when the argument is NULL. LNNVL consequently never returns +NULL. + +=== SQL Definition + +The function is defined in +`contrib/ivorysql_ora/src/builtin_functions/builtin_functions--1.0.sql`. + +[source,sql] +---- +CREATE FUNCTION sys.lnnvl(pg_catalog.bool) +RETURNS pg_catalog.bool +AS $$SELECT $1 IS NOT TRUE$$ +LANGUAGE sql +CALLED ON NULL INPUT +PARALLEL SAFE +IMMUTABLE; +---- + +Rationale for each property: + +[cols="1,1,3"] +|=== +|Property |Value |Rationale + +|Schema +|`sys` +|Consistent with the other Oracle compatibility functions. `sys` enters the +implicit `search_path` only in Oracle compatibility mode. + +|Argument type +|`pg_catalog.bool` +|Schema-qualified so that type name resolution is unaffected when `sys` is +first in the `search_path` in Oracle mode. + +|Language +|`sql` +|Can be folded by `inline_function()`, see below. A C function would not be +inlined. + +|NULL handling +|`CALLED ON NULL INPUT` +|Mandatory, see below. + +|Volatility +|`IMMUTABLE` +|Pure logic, same input always yields the same output; also a prerequisite for +use in expression indexes. + +|Parallelism +|`PARALLEL SAFE` +|No side effects. Without this clause the default is `PARALLEL UNSAFE`, which +disables parallel plans for any query using the function. +|=== + +=== CALLED ON NULL INPUT Is Mandatory + +`CALLED ON NULL INPUT` is the SQL default; it is spelled out here to keep it +from being changed by mistake. + +Declared `STRICT`, the body would not run at all for a NULL argument and the +result would be NULL rather than true, which removes the only reason the +function exists, and it would do so silently. The risk comes from the pull +towards stylistic consistency: most neighbouring functions in +`builtin_functions--1.0.sql` are declared `STRICT`, so tidying this one to +match its neighbours introduces the bug. + +`STRICT` also defeats inlining. Declaring a function strict promises that NULL +in yields NULL out, while `IS NOT TRUE` is not itself a strict predicate, so +the planner cannot expand it while preserving semantics and has to keep the +function call: + +[source,text] +---- +-- CALLED ON NULL INPUT +Filter: (t.v IS NOT TRUE) + +-- STRICT +Filter: f_strict(t.v) +---- + +Writing `STRICT` here therefore costs twice: the semantics are wrong and +silently so, and performance degrades to a per-row function call. + +=== Function Inlining + +The body is a simple, inlinable SQL expression, so `inline_function()` folds +it into the caller: + +[source,sql] +---- +EXPLAIN (VERBOSE, COSTS OFF) SELECT id FROM t WHERE LNNVL(v >= 0.2); +---- + +[source,text] +---- + Seq Scan on public.t + Output: id + Filter: ((t.v >= 0.2) IS NOT TRUE) +---- + +No function call remains in the plan, which is identical to the plan for +writing `IS NOT TRUE` by hand, so there is no additional runtime cost. diff --git a/EN/modules/ROOT/pages/master/oracle_compatibility/compat_lnnvl.adoc b/EN/modules/ROOT/pages/master/oracle_compatibility/compat_lnnvl.adoc new file mode 100644 index 00000000..e762a337 --- /dev/null +++ b/EN/modules/ROOT/pages/master/oracle_compatibility/compat_lnnvl.adoc @@ -0,0 +1,267 @@ +:sectnums: +:sectnumlevels: 5 + +:imagesdir: ./_images + += LNNVL Function + +== Purpose + +This document describes the `sys.lnnvl` function in IvorySQL, which negates a +condition in an Oracle compatible way and still returns TRUE when the condition +evaluates to UNKNOWN. + +== Functional Description + +* `sys.lnnvl(boolean)` takes a condition and returns TRUE when it evaluates to + FALSE or UNKNOWN, and FALSE when it evaluates to TRUE. +* The function **never returns NULL**. The result is always TRUE or FALSE, so it + can be used directly as a WHERE condition without introducing a new UNKNOWN. +* Its main use is to eliminate the row loss caused by three-valued logic: with a + plain `NOT`, rows whose condition is UNKNOWN are dropped both by the original + condition and by its negation, whereas a condition and its LNNVL partition the + table strictly, so the two row counts always add up to the total. + +Truth table: + +[cols="1,1"] +|=== +|Condition evaluates to |Return value + +|TRUE +|FALSE + +|FALSE +|TRUE + +|UNKNOWN (condition involves NULL) +|TRUE +|=== + +== Syntax + +[source,sql] +---- +sys.lnnvl(condition) +---- + +[cols="1,3"] +|=== +|Parameter |Description + +|`condition` +|Any boolean expression. Non-boolean types are not accepted and there is no +implicit cast from integer. +|=== + +Return type: `boolean` + +== Test Cases + +=== Test Environment Setup + +[source,sql] +---- +CREATE TABLE lnnvl_test (a INT, b INT); +INSERT INTO lnnvl_test VALUES (2, NULL); +---- + +=== Truth Table Verification + +[source,sql] +---- +-- a = 2, b is NULL +SELECT LNNVL(a = 1) FROM lnnvl_test; -- expected: t (condition is FALSE) +SELECT LNNVL(a = 2) FROM lnnvl_test; -- expected: f (condition is TRUE) +SELECT LNNVL(a IS NULL) FROM lnnvl_test; -- expected: t (condition is FALSE) +SELECT LNNVL(b = 1) FROM lnnvl_test; -- expected: t (condition is UNKNOWN) +SELECT LNNVL(b IS NULL) FROM lnnvl_test; -- expected: f (condition is TRUE) +SELECT LNNVL(a = b) FROM lnnvl_test; -- expected: t (condition is UNKNOWN) +---- + +=== NULL Argument + +[source,sql] +---- +-- A NULL argument must return t; this is the core behaviour of LNNVL +SELECT LNNVL(NULL); -- expected: t +SELECT LNNVL(NULL::boolean); -- expected: t +---- + +=== Partition Property + +[source,sql] +---- +DROP TABLE lnnvl_test; +CREATE TABLE lnnvl_test (id INT, v NUMERIC); +INSERT INTO lnnvl_test VALUES (1,0.30),(2,0.10),(3,NULL),(4,0.25),(5,NULL); + +SELECT count(*) FROM lnnvl_test WHERE v >= 0.2; -- expected: 2 +SELECT count(*) FROM lnnvl_test WHERE LNNVL(v >= 0.2); -- expected: 3 +SELECT count(*) FROM lnnvl_test; -- expected: 5 + +-- 2 + 3 = 5: the condition and its LNNVL cover the whole table, no row is lost +-- With NOT, the two counts add up to less than the total +SELECT count(*) FROM lnnvl_test WHERE NOT (v >= 0.2); -- expected: 2, total only 4 +---- + +=== NOT IN over a Subquery Containing NULL + +[source,sql] +---- +CREATE TABLE lnnvl_bl (d INT); +INSERT INTO lnnvl_bl VALUES (1), (NULL); + +-- With a NULL in the list, NOT IN matches nothing +SELECT count(*) FROM lnnvl_test WHERE id NOT IN (SELECT d FROM lnnvl_bl); +-- expected: 0 + +-- Rewritten with LNNVL(... IN ...) it gives the intuitive result +SELECT count(*) FROM lnnvl_test WHERE LNNVL(id IN (SELECT d FROM lnnvl_bl)); +-- expected: 4 +---- + +`id NOT IN (1, NULL)` expands to `id <> 1 AND id <> NULL`, where +`id <> NULL` is always UNKNOWN, so the whole condition is never TRUE and every +row is dropped. `LNNVL(id IN (...))` treats UNKNOWN as "not true" and keeps +those rows. + +=== Argument Type Checking + +[source,sql] +---- +-- The argument must be boolean; there is no implicit cast from integer +SELECT LNNVL(1); +-- expected error: function lnnvl(pg_catalog.int4) does not exist +---- + +=== Test Environment Cleanup + +[source,sql] +---- +DROP TABLE lnnvl_bl; +DROP TABLE lnnvl_test; +---- + +== Behavioral Differences from Oracle + +Oracle's grammar accepts only a single simple condition. IvorySQL implements +LNNVL as an ordinary function, and by the time overload resolution runs its +argument is already a boolean expression: the `AND`, `OR`, `NOT` or `BETWEEN` +that produced it is no longer visible to the function. The restrictions +therefore cannot be reproduced, and **the accepted forms are a superset of +Oracle's**. + +The direction is safe: any LNNVL usage that runs on Oracle also runs on +IvorySQL, but not the reverse -- a compound form written on IvorySQL will raise +an error when moved back to Oracle. + +[cols="2,1,1"] +|=== +|Argument form |Oracle |IvorySQL + +|`LNNVL(1 = 1 AND 2 = 2)` +|rejected +|accepted, returns f + +|`LNNVL(1 = 1 OR 2 = 2)` +|rejected +|accepted, returns f + +|`LNNVL(1 BETWEEN 0 AND 5)` +|rejected +|accepted, returns f + +|`LNNVL(1 NOT BETWEEN 0 AND 5)` +|rejected +|accepted, returns t + +|`LNNVL(NOT (1 = 1))` +|rejected (ORA-00936) +|accepted, returns t + +|`LNNVL(NOT EXISTS (SELECT ...))` +|rejected (ORA-00936) +|accepted, returns t + +|`LNNVL(1 IN (1,2))` +|rejected (ORA-13207) +|accepted, returns f + +|`LNNVL(1 NOT IN (1,2))` +|rejected (ORA-13207) +|accepted, returns t +|=== + +Other differences: + +[cols="1,2,2"] +|=== +|Aspect |Oracle |IvorySQL + +|Error codes and text +|`ORA-` numbers +|PostgreSQL SQLSTATE values and wording + +|`LNNVL(1)` +|Handled by its BOOLEAN type conversion rules +|Reports that no such function exists; no implicit cast +|=== + +To express a compound condition, follow the Oracle practice of splitting it +into several LNNVL calls combined with `AND` or `OR`: + +[source,sql] +---- +SELECT LNNVL(1 = 1) OR LNNVL(2 = 3); -- expected: f +SELECT LNNVL(1 = 1) AND LNNVL(2 = 3); -- expected: f +SELECT NOT LNNVL(1 = 1); -- expected: t +---- + +Note that De Morgan's laws apply when splitting: `LNNVL(A AND B)` corresponds to +`LNNVL(A) OR LNNVL(B)`; using `AND` changes the result. + +== Comparison with NOT + +[cols="1,2,2"] +|=== +|Condition evaluates to |`NOT cond` |`LNNVL(cond)` + +|TRUE +|FALSE +|FALSE + +|FALSE +|TRUE +|TRUE + +|UNKNOWN +|UNKNOWN (row dropped) +|TRUE (row kept) +|=== + +The two treat "unknown" in opposite directions: `NOT` discards it, LNNVL keeps +it. Strictly speaking the behaviour of `NOT` is correct under three-valued +logic; what LNNVL does is make the assumption "unknown means the original +condition is not satisfied" explicit, which is usually the intended result in +practice. + +== Equivalent Formulations + +`LNNVL(cond)` is equivalent to the SQL standard predicate +`cond IS NOT TRUE`. Where Oracle compatible syntax is not required, the latter +can be used directly; the execution plans are identical. + +For the NOT IN with NULL case, the idiomatic PostgreSQL solution is +`NOT EXISTS`: + +[source,sql] +---- +SELECT count(*) FROM lnnvl_test t + WHERE NOT EXISTS (SELECT 1 FROM lnnvl_bl b WHERE b.d = t.id); +---- + +That form gives the same result as `LNNVL(id IN (...))` and is more portable. +The advantage of LNNVL is the cost of rewriting: it wraps an existing condition +in place, whereas `NOT EXISTS` requires restructuring the subquery into a +correlated one.