Skip to content

Commit 7a68e5e

Browse files
authored
Report non-decimal integer literals (#2217)
Fixes #1982 Report the non-decimal integer literals wherever possible in Schema with some caveats listed in the code.
1 parent ae842f5 commit 7a68e5e

File tree

16 files changed

+295
-20
lines changed

16 files changed

+295
-20
lines changed

migtests/tests/analyze-schema/dummy-export-dir/schema/functions/function.sql

+21-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,26 @@ BEGIN
175175
END;
176176
$$ LANGUAGE plpgsql;
177177

178+
CREATE FUNCTION public.insert_non_decimal() RETURNS void
179+
LANGUAGE plpgsql
180+
AS $$
181+
BEGIN
182+
-- Create a table for demonstration
183+
CREATE TEMP TABLE non_decimal_table (
184+
id SERIAL,
185+
binary_value INTEGER,
186+
octal_value INTEGER,
187+
hex_value INTEGER
188+
);
189+
SELECT 5678901234, 0x1527D27F2, 0o52237223762, 0b101010010011111010010011111110010;
190+
-- Insert values into the table
191+
--not reported as parser converted these values to decimal ones while giving parseTree
192+
INSERT INTO non_decimal_table (binary_value, octal_value, hex_value)
193+
VALUES (0b1010, 0o012, 0xA); -- Binary (10), Octal (10), Hexadecimal (10)
194+
RAISE NOTICE 'Row inserted with non-decimal integers.';
195+
END;
196+
$$;
197+
178198
CREATE FUNCTION public.asterisks(n integer) RETURNS SETOF text
179199
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
180200
BEGIN ATOMIC
@@ -187,4 +207,4 @@ CREATE FUNCTION add(int, int) RETURNS int IMMUTABLE PARALLEL SAFE BEGIN ATOMIC;
187207

188208
CREATE FUNCTION public.asterisks1(n integer) RETURNS text
189209
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
190-
RETURN repeat('*'::text, n);
210+
RETURN repeat('*'::text, n);

migtests/tests/analyze-schema/dummy-export-dir/schema/views/view.sql

+7
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,10 @@ SELECT jt.* FROM
4949
NESTED PATH '$.films[*]' COLUMNS (
5050
title text FORMAT JSON PATH '$.title' OMIT QUOTES,
5151
director text PATH '$.director' KEEP QUOTES))) AS jt;
52+
53+
CREATE VIEW zz AS
54+
SELECT
55+
5678901234 AS DEC,
56+
0x1527D27F2 AS hex,
57+
0o52237223762 AS oct,
58+
0b101010010011111010010011111110010 AS bin;

migtests/tests/analyze-schema/expected_issues.json

+22
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,17 @@
747747
"DocsLink":"https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#referencing-clause-for-triggers",
748748
"MinimumVersionsFixedIn": null
749749
},
750+
{
751+
"IssueType": "unsupported_plpgsql_objects",
752+
"ObjectType": "FUNCTION",
753+
"ObjectName": "public.insert_non_decimal",
754+
"Reason": "Non decimal integer literals are not supported in YugabyteDB",
755+
"SqlStatement": "SELECT 5678901234, 0x1527D27F2, 0o52237223762, 0b101010010011111010010011111110010;",
756+
"Suggestion": "",
757+
"GH": "https://github.com/yugabyte/yugabyte-db/issues/25575",
758+
"DocsLink": "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features",
759+
"MinimumVersionsFixedIn": null
760+
},
750761
{
751762
"IssueType": "unsupported_features",
752763
"ObjectType": "TRIGGER",
@@ -2152,6 +2163,17 @@
21522163
"2.25": "2.25.0.0"
21532164
}
21542165
},
2166+
{
2167+
"IssueType": "unsupported_features",
2168+
"ObjectType": "VIEW",
2169+
"ObjectName": "zz",
2170+
"Reason": "Non decimal integer literals are not supported in YugabyteDB",
2171+
"SqlStatement": "CREATE VIEW zz AS\n SELECT\n 5678901234 AS DEC,\n 0x1527D27F2 AS hex,\n 0o52237223762 AS oct,\n 0b101010010011111010010011111110010 AS bin;",
2172+
"Suggestion": "",
2173+
"GH": "https://github.com/yugabyte/yugabyte-db/issues/25575",
2174+
"DocsLink": "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features",
2175+
"MinimumVersionsFixedIn": null
2176+
},
21552177
{
21562178
"IssueType": "unsupported_datatypes",
21572179
"ObjectType": "TABLE",

migtests/tests/analyze-schema/summary.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@
4444
},
4545
{
4646
"ObjectType": "FUNCTION",
47-
"TotalCount": 10,
48-
"InvalidCount": 10,
49-
"ObjectNames": "public.asterisks, add, public.asterisks1, create_and_populate_tables, public.get_employeee_salary, get_employee_details, calculate_tax, log_salary_change, list_high_earners, copy_high_earners"
47+
"TotalCount": 11,
48+
"InvalidCount": 11,
49+
"ObjectNames": "public.insert_non_decimal, public.asterisks, add, public.asterisks1, create_and_populate_tables, public.get_employeee_salary, get_employee_details, calculate_tax, log_salary_change, list_high_earners, copy_high_earners"
5050
},
5151
{
5252
"ObjectType": "PROCEDURE",
@@ -56,9 +56,9 @@
5656
},
5757
{
5858
"ObjectType": "VIEW",
59-
"TotalCount": 7,
60-
"InvalidCount": 7,
61-
"ObjectNames": "public.my_films_view, v1, v2, test, public.orders_view, view_name, top_employees_view"
59+
"TotalCount": 8,
60+
"InvalidCount": 8,
61+
"ObjectNames": "zz, public.my_films_view, v1, v2, test, public.orders_view, view_name, top_employees_view"
6262
},
6363
{
6464
"ObjectType": "TRIGGER",

migtests/tests/pg/assessment-report-test-uqc/unsupported_query_constructs.sql

+7-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ SELECT *
6060
FROM sales.json_data
6161
WHERE array_column IS JSON ARRAY;
6262

63+
-- PG 15 supports the non-decimal integer literals e.g. hexadecimal, octal, binary
64+
-- but this won't be reported as the PGSS will change the constant integers to parameters - $1, $2...
65+
SELECT 1234, 0x4D2 as hex, 0o2322 as octal, 0b10011010010 as binary;
66+
67+
SELECT 5678901234, 0x1527D27F2, 0o52237223762, 0b101010010011111010010011111110010 \gdesc
68+
6369
WITH w AS NOT MATERIALIZED (
6470
SELECT * FROM sales.big_table
6571
)
@@ -76,4 +82,4 @@ WITH w AS (
7682
SELECT * FROM sales.big_table
7783
)
7884
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref
79-
WHERE w2.key = 123;
85+
WHERE w2.key = 123;

migtests/tests/pg/assessment-report-test/expectedAssessmentReport.json

+22-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"VoyagerVersion": "IGNORED",
33
"TargetDBVersion": "IGNORED",
44
"MigrationComplexity": "HIGH",
5-
"MigrationComplexityExplanation": "Found 26 Level 2 issue(s) and 29 Level 3 issue(s), resulting in HIGH migration complexity",
5+
"MigrationComplexityExplanation": "Found 28 Level 2 issue(s) and 29 Level 3 issue(s), resulting in HIGH migration complexity",
66
"SchemaSummary": {
77
"Description": "Objects that will be created on the target YugabyteDB.",
88
"DbName": "pg_assessment_report",
@@ -57,9 +57,10 @@
5757
},
5858
{
5959
"ObjectType": "FUNCTION",
60-
"TotalCount": 15,
61-
"InvalidCount": 8,
62-
"ObjectNames": "public.asterisks, schema2.asterisks, public.asterisks1, schema2.asterisks1, public.manage_large_object, public.auditlogfunc, public.check_sales_region, public.prevent_update_shipped_without_date, public.process_combined_tbl, public.process_order, public.total, schema2.auditlogfunc, schema2.prevent_update_shipped_without_date, schema2.process_order, schema2.total" },
60+
"TotalCount": 17,
61+
"InvalidCount": 10,
62+
"ObjectNames": "public.insert_non_decimal, schema2.insert_non_decimal, public.asterisks, schema2.asterisks, public.asterisks1, schema2.asterisks1, public.manage_large_object, public.auditlogfunc, public.check_sales_region, public.prevent_update_shipped_without_date, public.process_combined_tbl, public.process_order, public.total, schema2.auditlogfunc, schema2.prevent_update_shipped_without_date, schema2.process_order, schema2.total"
63+
},
6364
{
6465
"ObjectType": "AGGREGATE",
6566
"TotalCount": 2,
@@ -2931,6 +2932,23 @@
29312932
}
29322933
],
29332934
"UnsupportedPlPgSqlObjects": [
2935+
{
2936+
"FeatureName": "Non-decimal integer literal",
2937+
"Objects": [
2938+
{
2939+
"ObjectType": "FUNCTION",
2940+
"ObjectName": "public.insert_non_decimal",
2941+
"SqlStatement": "SELECT 5678901234, 0x1527D27F2, 0o52237223762, 0b101010010011111010010011111110010;"
2942+
},
2943+
{
2944+
"ObjectType": "FUNCTION",
2945+
"ObjectName": "schema2.insert_non_decimal",
2946+
"SqlStatement": "SELECT 5678901234, 0x1527D27F2, 0o52237223762, 0b101010010011111010010011111110010;"
2947+
}
2948+
],
2949+
"DocsLink": "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features",
2950+
"MinimumVersionsFixedIn": null
2951+
},
29342952
{
29352953
"FeatureName": "Large Object Functions",
29362954
"Objects": [

migtests/tests/pg/assessment-report-test/pg_assessment_report.sql

+20-1
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,25 @@ CREATE UNIQUE INDEX users_unique_nulls_not_distinct_index_email
478478
NULLS NOT DISTINCT;
479479

480480

481+
CREATE OR REPLACE FUNCTION insert_non_decimal()
482+
RETURNS VOID AS $$
483+
BEGIN
484+
-- Create a table for demonstration
485+
CREATE TEMP TABLE non_decimal_table (
486+
id SERIAL,
487+
binary_value INTEGER,
488+
octal_value INTEGER,
489+
hex_value INTEGER
490+
);
491+
SELECT 5678901234, 0x1527D27F2, 0o52237223762, 0b101010010011111010010011111110010;
492+
-- Insert values into the table
493+
--not reported as parser converted these values to decimal ones while giving parseTree
494+
INSERT INTO non_decimal_table (binary_value, octal_value, hex_value)
495+
VALUES (0b1010, 0o012, 0xA); -- Binary (10), Octal (10), Hexadecimal (10)
496+
497+
RAISE NOTICE 'Row inserted with non-decimal integers.';
498+
END;
499+
$$ LANGUAGE plpgsql;
481500

482501
CREATE OR REPLACE FUNCTION asterisks(n int)
483502
RETURNS SETOF text
@@ -490,4 +509,4 @@ END;
490509
CREATE OR REPLACE FUNCTION asterisks1(n int)
491510
RETURNS text
492511
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
493-
RETURN repeat('*', n);
512+
RETURN repeat('*', n);

yb-voyager/cmd/assessMigrationCommand.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1063,7 +1063,7 @@ func fetchUnsupportedPGFeaturesFromSchemaReport(schemaAnalysisReport utils.Schem
10631063
unsupportedFeatures = append(unsupportedFeatures, getUnsupportedFeaturesFromSchemaAnalysisReport(queryissue.JSON_TYPE_PREDICATE_NAME, "", queryissue.JSON_TYPE_PREDICATE, schemaAnalysisReport, false))
10641064
unsupportedFeatures = append(unsupportedFeatures, getUnsupportedFeaturesFromSchemaAnalysisReport(queryissue.SQL_BODY_IN_FUNCTION_NAME, "", queryissue.SQL_BODY_IN_FUNCTION, schemaAnalysisReport, false))
10651065
unsupportedFeatures = append(unsupportedFeatures, getUnsupportedFeaturesFromSchemaAnalysisReport(queryissue.CTE_WITH_MATERIALIZED_CLAUSE_NAME, "", queryissue.CTE_WITH_MATERIALIZED_CLAUSE, schemaAnalysisReport, false))
1066-
1066+
unsupportedFeatures = append(unsupportedFeatures, getUnsupportedFeaturesFromSchemaAnalysisReport(queryissue.NON_DECIMAL_INTEGER_LITERAL_NAME, "", queryissue.NON_DECIMAL_INTEGER_LITERAL, schemaAnalysisReport, false))
10671067
return lo.Filter(unsupportedFeatures, func(f UnsupportedFeature, _ int) bool {
10681068
return len(f.Objects) > 0
10691069
}), nil

yb-voyager/src/query/queryissue/constants.go

+3
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ const (
9797
SQL_BODY_IN_FUNCTION_NAME = "SQL Body in function"
9898
UNIQUE_NULLS_NOT_DISTINCT = "UNIQUE_NULLS_NOT_DISTINCT"
9999
UNIQUE_NULLS_NOT_DISTINCT_NAME = "Unique Nulls Not Distinct"
100+
101+
NON_DECIMAL_INTEGER_LITERAL = "NON_DECIMAL_INTEGER_LITERAL"
102+
NON_DECIMAL_INTEGER_LITERAL_NAME = "Non-decimal integer literal"
100103
)
101104

102105
const (

yb-voyager/src/query/queryissue/detectors.go

+72-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package queryissue
1717

1818
import (
1919
"slices"
20+
"strings"
2021

2122
mapset "github.com/deckarep/golang-set/v2"
2223
log "github.com/sirupsen/logrus"
@@ -564,6 +565,75 @@ func (j *JsonPredicateExprDetector) GetIssues() []QueryIssue {
564565
return issues
565566
}
566567

568+
type NonDecimalIntegerLiteralDetector struct {
569+
query string
570+
detected bool
571+
}
572+
573+
func NewNonDecimalIntegerLiteralDetector(query string) *NonDecimalIntegerLiteralDetector {
574+
return &NonDecimalIntegerLiteralDetector{
575+
query: query,
576+
}
577+
}
578+
func (n *NonDecimalIntegerLiteralDetector) Detect(msg protoreflect.Message) error {
579+
if queryparser.GetMsgFullName(msg) != queryparser.PG_QUERY_ACONST_NODE {
580+
return nil
581+
}
582+
aConstNode, err := queryparser.ProtoAsAConstNode(msg)
583+
if err != nil {
584+
return err
585+
}
586+
/*
587+
Caveats can't report this issue for cases like -
588+
1. DML having the constant change to parameters in PGSS - SELECT $1, $2 as binary;
589+
2. DDL having the CHECK or DEFAULT -
590+
- pg_dump will not dump non-decimal literal, it will give out the decimal constant only
591+
- even if in some user added DDL in schema file during analyze-schema it can't be detected as parse tree doesn't info
592+
e.g. -
593+
CREATE TABLE bitwise_example (
594+
id SERIAL PRIMARY KEY,
595+
flags INT DEFAULT 0x0F CHECK (flags & 0x01 = 0x01) -- Hexadecimal bitwise check
596+
);
597+
parseTree - create_stmt:{relation:{relname:"bitwise_example" inh:true relpersistence:"p" location:15} ...
598+
table_elts:{column_def:{colname:"flags" type_name:{names:{string:{sval:"pg_catalog"}} names:{string:{sval:"int4"}} typemod:-1
599+
location:70} is_local:true constraints:{constraint:{contype:CONSTR_DEFAULT raw_expr:{a_const:{ival:{ival:15} location:82}}
600+
location:74}} constraints:{constraint:{contype:CONSTR_CHECK initially_valid:true raw_expr:{a_expr:{kind:AEXPR_OP name:{string:{sval:"="}}
601+
lexpr:{a_expr:{kind:AEXPR_OP name:{string:{sval:"&"}} lexpr:{column_ref:{fields:{string:{sval:"flags"}} location:94}} rexpr:{a_const:{ival:{ival:1}
602+
location:102}} location:100}} rexpr:{a_const:{ival:{ival:1} location:109}} location:107}} ..
603+
604+
So mostly be detecting this in PLPGSQL cases
605+
*/
606+
switch {
607+
case aConstNode.GetFval() != nil:
608+
/*
609+
fval - float val representation in postgres
610+
ival - integer val
611+
Fval is only one which stores the non-decimal integers information if used in queries, e.g. SELECT 5678901234, 0o52237223762 as octal;
612+
select_stmt:{target_list:{res_target:{val:{a_const:{fval:{fval:"5678901234"} location:9}} location:9}}
613+
target_list:{res_target:{name:"octal" val:{a_const:{fval:{fval:"0o52237223762"} location:21}} location:21}}
614+
615+
ival stores the decimal integers if non-decimal is not used in the query, e.g. SELECT 1, 2;
616+
select_stmt:{target_list:{res_target:{val:{a_const:{ival:{ival:1} location:8}} location:8}}
617+
target_list:{res_target:{val:{a_const:{ival:{ival:2} location:10}} location:10}}
618+
*/
619+
fval := aConstNode.GetFval().Fval
620+
for _, literal := range nonDecimalIntegerLiterals {
621+
if strings.HasPrefix(fval, literal) {
622+
n.detected = true
623+
}
624+
}
625+
}
626+
return nil
627+
}
628+
629+
func (n *NonDecimalIntegerLiteralDetector) GetIssues() []QueryIssue {
630+
var issues []QueryIssue
631+
if n.detected {
632+
issues = append(issues, NewNonDecimalIntegerLiteralIssue(DML_QUERY_OBJECT_TYPE, "", n.query))
633+
}
634+
return issues
635+
}
636+
567637
type CommonTableExpressionDetector struct {
568638
query string
569639
materializedClauseDetected bool
@@ -580,8 +650,8 @@ func (c *CommonTableExpressionDetector) Detect(msg protoreflect.Message) error {
580650
return nil
581651
}
582652
/*
583-
with_clause:{ctes:{common_table_expr:{ctename:"cte" ctematerialized:CTEMaterializeNever
584-
ctequery:{select_stmt:{target_list:{res_target:{val:{column_ref:{fields:{a_star:{}} location:939}} location:939}} from_clause:{range_var:{relname:"a" inh:true relpersistence:"p" location:946}} limit_option:LIMIT_OPTION_DEFAULT op:SETOP_NONE}} location:906}} location:901} op:SETOP_NONE}} stmt_location:898
653+
with_clause:{ctes:{common_table_expr:{ctename:"cte" ctematerialized:CTEMaterializeNever
654+
ctequery:{select_stmt:{target_list:{res_target:{val:{column_ref:{fields:{a_star:{}} location:939}} location:939}} from_clause:{range_var:{relname:"a" inh:true relpersistence:"p" location:946}} limit_option:LIMIT_OPTION_DEFAULT op:SETOP_NONE}} location:906}} location:901} op:SETOP_NONE}} stmt_location:898
585655
*/
586656
cteNode, err := queryparser.ProtoAsCTENode(msg)
587657
if err != nil {
@@ -601,4 +671,3 @@ func (c *CommonTableExpressionDetector) GetIssues() []QueryIssue {
601671
}
602672
return issues
603673
}
604-

yb-voyager/src/query/queryissue/helpers.go

+11
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,14 @@ var catalogFunctionsReturningJsonb = mapset.NewThreadUnsafeSet([]string{
159159
"jsonb_path_query_array_tz", "jsonb_path_query_first", "jsonb_path_query_first_tz", "jsonb_recv",
160160
"jsonb_set", "jsonb_set_lax", "jsonb_strip_nulls", "to_jsonb", "ts_headline",
161161
}...)
162+
163+
var nonDecimalIntegerLiterals = []string{
164+
"0x",
165+
"0X",
166+
"0o",
167+
"0O",
168+
"0b",
169+
"0B",
170+
//https://github.com/pganalyze/pg_query_go/blob/38c866daa3fdb0a7af78741476d6b89029c19afe/parser/src_backend_utils_adt_numutils.c#L59C30-L61C76
171+
// the prefix "0x" could be "0X" as well so should check both
172+
}

yb-voyager/src/query/queryissue/issues_dml.go

+14
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,17 @@ func NewMergeStatementIssue(objectType string, objectName string, sqlStatement s
277277
//MERGE STATEMENT is PG15 feature but MERGE .... RETURNING clause is PG17 feature so need to report it separately later.
278278
return newQueryIssue(mergeStatementIssue, objectType, objectName, sqlStatement, map[string]interface{}{})
279279
}
280+
281+
var nonDecimalIntegerLiteralIssue = issue.Issue{
282+
Type: NON_DECIMAL_INTEGER_LITERAL,
283+
Name: NON_DECIMAL_INTEGER_LITERAL_NAME,
284+
Impact: constants.IMPACT_LEVEL_2,
285+
Description: "Non decimal integer literals are not supported in YugabyteDB",
286+
Suggestion: "",
287+
GH: "https://github.com/yugabyte/yugabyte-db/issues/25575",
288+
DocsLink: "https://docs.yugabyte.com/preview/yugabyte-voyager/known-issues/postgresql/#postgresql-12-and-later-features",
289+
}
290+
291+
func NewNonDecimalIntegerLiteralIssue(objectType string, objectName string, sqlStatement string) QueryIssue {
292+
return newQueryIssue(nonDecimalIntegerLiteralIssue, objectType, objectName, sqlStatement, map[string]interface{}{})
293+
}

0 commit comments

Comments
 (0)