pg_query 6.1.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -1
  3. data/README.md +1 -1
  4. data/Rakefile +3 -2
  5. data/ext/pg_query/extconf.rb +2 -2
  6. data/ext/pg_query/include/pg_query.h +32 -2
  7. data/ext/pg_query/include/postgres/access/amapi.h +1 -1
  8. data/ext/pg_query/include/postgres/access/slru.h +1 -1
  9. data/ext/pg_query/include/postgres/access/tableam.h +11 -4
  10. data/ext/pg_query/include/postgres/access/xlog.h +1 -0
  11. data/ext/pg_query/include/postgres/access/xlogdefs.h +2 -1
  12. data/ext/pg_query/include/postgres/commands/defrem.h +1 -1
  13. data/ext/pg_query/include/postgres/commands/trigger.h +18 -0
  14. data/ext/pg_query/include/postgres/executor/executor.h +4 -0
  15. data/ext/pg_query/include/postgres/mb/pg_wchar.h +2 -0
  16. data/ext/pg_query/include/postgres/miscadmin.h +2 -1
  17. data/ext/pg_query/include/postgres/nodes/execnodes.h +6 -8
  18. data/ext/pg_query/include/postgres/nodes/pathnodes.h +1 -2
  19. data/ext/pg_query/include/postgres/pg_config.h +10 -9
  20. data/ext/pg_query/include/postgres/pg_config_manual.h +2 -0
  21. data/ext/pg_query/include/postgres/port/atomics/generic-gcc.h +10 -2
  22. data/ext/pg_query/include/postgres/port/pg_iovec.h +9 -3
  23. data/ext/pg_query/include/postgres/replication/reorderbuffer.h +29 -9
  24. data/ext/pg_query/include/postgres/replication/slot.h +2 -0
  25. data/ext/pg_query/include/postgres/utils/elog.h +1 -0
  26. data/ext/pg_query/include/postgres/utils/guc.h +1 -1
  27. data/ext/pg_query/include/postgres/utils/guc_hooks.h +0 -2
  28. data/ext/pg_query/include/postgres/utils/pg_locale.h +5 -0
  29. data/ext/pg_query/include/postgres_deparse.h +34 -0
  30. data/ext/pg_query/include/protobuf/pg_query.pb-c.h +673 -516
  31. data/ext/pg_query/pg_query.pb-c.c +488 -0
  32. data/ext/pg_query/pg_query_deparse.c +148 -15
  33. data/ext/pg_query/pg_query_internal.h +9 -8
  34. data/ext/pg_query/pg_query_is_utility_stmt.c +70 -0
  35. data/ext/pg_query/pg_query_normalize.c +3 -0
  36. data/ext/pg_query/pg_query_raw_tree_walker_supports.c +117 -0
  37. data/ext/pg_query/pg_query_ruby.c +150 -0
  38. data/ext/pg_query/pg_query_summary.c +941 -0
  39. data/ext/pg_query/pg_query_summary.h +109 -0
  40. data/ext/pg_query/pg_query_summary_statement_type.c +797 -0
  41. data/ext/pg_query/pg_query_summary_truncate.c +530 -0
  42. data/ext/pg_query/postgres_deparse.c +4481 -3870
  43. data/ext/pg_query/src_backend_catalog_namespace.c +29 -0
  44. data/ext/pg_query/src_backend_nodes_bitmapset.c +84 -1
  45. data/ext/pg_query/src_backend_nodes_list.c +60 -1
  46. data/ext/pg_query/src_backend_parser_gram.c +739 -732
  47. data/ext/pg_query/src_backend_utils_activity_pgstat_database.c +2 -2
  48. data/ext/pg_query/src_backend_utils_error_elog.c +11 -0
  49. data/ext/pg_query/src_backend_utils_mb_mbutils.c +43 -4
  50. data/ext/pg_query/src_backend_utils_mmgr_alignedalloc.c +22 -7
  51. data/ext/pg_query/src_backend_utils_mmgr_aset.c +3 -3
  52. data/ext/pg_query/src_backend_utils_mmgr_bump.c +1 -1
  53. data/ext/pg_query/src_common_stringinfo.c +20 -0
  54. data/ext/pg_query/src_common_wchar.c +46 -6
  55. data/lib/pg_query/deparse.rb +29 -8
  56. data/lib/pg_query/parse.rb +19 -0
  57. data/lib/pg_query/pg_query_pb.rb +7 -2
  58. data/lib/pg_query/split.rb +20 -0
  59. data/lib/pg_query/treewalker.rb +9 -7
  60. data/lib/pg_query/version.rb +1 -1
  61. data/lib/pg_query.rb +1 -0
  62. metadata +10 -3
  63. data/ext/pg_query/postgres_deparse.h +0 -9
@@ -8,17 +8,18 @@
8
8
  #define STDERR_BUFFER_LEN 4096
9
9
  #define DEBUG
10
10
 
11
- typedef struct {
12
- List *tree;
13
- char* stderr_buffer;
14
- PgQueryError* error;
15
- } PgQueryInternalParsetreeAndError;
11
+ typedef struct
12
+ {
13
+ List *tree;
14
+ char *stderr_buffer;
15
+ PgQueryError *error;
16
+ } PgQueryInternalParsetreeAndError;
16
17
 
17
- PgQueryInternalParsetreeAndError pg_query_raw_parse(const char* input, int parser_options);
18
+ PgQueryInternalParsetreeAndError pg_query_raw_parse(const char *input, int parser_options);
18
19
 
19
- void pg_query_free_error(PgQueryError *error);
20
+ void pg_query_free_error(PgQueryError * error);
20
21
 
21
22
  MemoryContext pg_query_enter_memory_context();
22
- void pg_query_exit_memory_context(MemoryContext ctx);
23
+ void pg_query_exit_memory_context(MemoryContext ctx);
23
24
 
24
25
  #endif
@@ -0,0 +1,70 @@
1
+ #include <stdbool.h>
2
+ #include <stddef.h>
3
+ #include <stdlib.h>
4
+
5
+ #include "pg_query.h"
6
+ #include "pg_query_internal.h"
7
+ #include <nodes/nodes.h>
8
+ #include <nodes/nodeFuncs.h>
9
+
10
+ static
11
+ bool
12
+ is_utility_stmt_actual(RawStmt *raw_stmt)
13
+ {
14
+ switch (nodeTag(raw_stmt->stmt))
15
+ {
16
+ case T_SelectStmt:
17
+ case T_InsertStmt:
18
+ case T_UpdateStmt:
19
+ case T_DeleteStmt:
20
+ case T_MergeStmt:
21
+ return false;
22
+
23
+ default:
24
+ return true;
25
+ }
26
+ }
27
+
28
+ PgQueryIsUtilityResult
29
+ pg_query_is_utility_stmt(const char *query)
30
+ {
31
+ PgQueryIsUtilityResult result = {0};
32
+ MemoryContext ctx = pg_query_enter_memory_context();
33
+
34
+ PgQueryInternalParsetreeAndError parsetree_and_error = pg_query_raw_parse(query, 0);
35
+
36
+ if (parsetree_and_error.error)
37
+ {
38
+ result.error = parsetree_and_error.error;
39
+ }
40
+ else
41
+ {
42
+ ListCell *lc;
43
+
44
+ result.length = list_length(parsetree_and_error.tree);
45
+ result.items = malloc(sizeof(bool) * result.length);
46
+
47
+ foreach(lc, parsetree_and_error.tree)
48
+ {
49
+ RawStmt *raw_stmt = lfirst_node(RawStmt, lc);
50
+
51
+ result.items[foreach_current_index(lc)] = is_utility_stmt_actual(raw_stmt);
52
+ }
53
+ }
54
+
55
+ if (parsetree_and_error.stderr_buffer)
56
+ free(parsetree_and_error.stderr_buffer);
57
+
58
+ pg_query_exit_memory_context(ctx);
59
+
60
+ return result;
61
+ }
62
+
63
+ void
64
+ pg_query_free_is_utility_result(PgQueryIsUtilityResult result)
65
+ {
66
+ if (result.error)
67
+ pg_query_free_error(result.error);
68
+
69
+ free(result.items);
70
+ }
@@ -406,6 +406,9 @@ static bool const_record_walker(Node *node, pgssConstLocations *jstate)
406
406
  case T_CopyStmt:
407
407
  if (jstate->normalize_utility_only) return false;
408
408
  return const_record_walker((Node *) ((CopyStmt *) node)->query, jstate);
409
+ case T_CallStmt:
410
+ if (jstate->normalize_utility_only) return false;
411
+ return const_record_walker((Node *) ((CallStmt *) node)->funccall, jstate);
409
412
  case T_ExplainStmt:
410
413
  return const_record_walker((Node *) ((ExplainStmt *) node)->query, jstate);
411
414
  case T_CreateRoleStmt:
@@ -0,0 +1,117 @@
1
+ #include <stdbool.h>
2
+
3
+ #include "pg_query.h"
4
+ #include "pg_query_internal.h"
5
+ #include "pg_query_outfuncs.h"
6
+
7
+ #include "parser/parser.h"
8
+ #include "parser/scanner.h"
9
+ #include "parser/scansup.h"
10
+
11
+ #include "nodes/execnodes.h"
12
+ #include "nodes/nodeFuncs.h"
13
+ #include "nodes/pathnodes.h"
14
+
15
+ #include "utils/builtins.h"
16
+
17
+ /*
18
+ * Returns true if raw_expression_tree_walker() handles it,
19
+ * otherwise, returns false.
20
+ *
21
+ * Up-to-date with postgres 17' implementation as of Aug 19 2025.
22
+ * https://github.com/postgres/postgres/blob/REL_17_STABLE/src/backend/nodes/nodeFuncs.c#L3964
23
+ */
24
+ bool
25
+ pg_query_raw_tree_walker_supports(Node *node)
26
+ {
27
+ switch (nodeTag(node))
28
+ {
29
+ case T_JsonFormat:
30
+ case T_SetToDefault:
31
+ case T_CurrentOfExpr:
32
+ case T_SQLValueFunction:
33
+ case T_Integer:
34
+ case T_Float:
35
+ case T_Boolean:
36
+ case T_String:
37
+ case T_BitString:
38
+ case T_ParamRef:
39
+ case T_A_Const:
40
+ case T_A_Star:
41
+ case T_MergeSupportFunc:
42
+ case T_Alias:
43
+ case T_RangeVar:
44
+ case T_GroupingFunc:
45
+ case T_SubLink:
46
+ case T_CaseExpr:
47
+ case T_RowExpr:
48
+ case T_CoalesceExpr:
49
+ case T_MinMaxExpr:
50
+ case T_XmlExpr:
51
+ case T_JsonReturning:
52
+ case T_JsonValueExpr:
53
+ case T_JsonParseExpr:
54
+ case T_JsonScalarExpr:
55
+ case T_JsonSerializeExpr:
56
+ case T_JsonConstructorExpr:
57
+ case T_JsonIsPredicate:
58
+ case T_JsonArgument:
59
+ case T_JsonFuncExpr:
60
+ case T_JsonBehavior:
61
+ case T_JsonTable:
62
+ case T_JsonTableColumn:
63
+ case T_JsonTablePathSpec:
64
+ case T_NullTest:
65
+ case T_BooleanTest:
66
+ case T_JoinExpr:
67
+ case T_IntoClause:
68
+ case T_List:
69
+ case T_InsertStmt:
70
+ case T_DeleteStmt:
71
+ case T_UpdateStmt:
72
+ case T_MergeStmt:
73
+ case T_MergeWhenClause:
74
+ case T_SelectStmt:
75
+ case T_PLAssignStmt:
76
+ case T_A_Expr:
77
+ case T_BoolExpr:
78
+ case T_ColumnRef:
79
+ case T_FuncCall:
80
+ case T_NamedArgExpr:
81
+ case T_A_Indices:
82
+ case T_A_Indirection:
83
+ case T_A_ArrayExpr:
84
+ case T_ResTarget:
85
+ case T_MultiAssignRef:
86
+ case T_TypeCast:
87
+ case T_CollateClause:
88
+ case T_SortBy:
89
+ case T_WindowDef:
90
+ case T_RangeSubselect:
91
+ case T_RangeFunction:
92
+ case T_RangeTableSample:
93
+ case T_RangeTableFunc:
94
+ case T_RangeTableFuncCol:
95
+ case T_TypeName:
96
+ case T_ColumnDef:
97
+ case T_IndexElem:
98
+ case T_GroupingSet:
99
+ case T_LockingClause:
100
+ case T_XmlSerialize:
101
+ case T_WithClause:
102
+ case T_InferClause:
103
+ case T_OnConflictClause:
104
+ case T_CommonTableExpr:
105
+ case T_JsonOutput:
106
+ case T_JsonKeyValue:
107
+ case T_JsonObjectConstructor:
108
+ case T_JsonArrayConstructor:
109
+ case T_JsonAggConstructor:
110
+ case T_JsonObjectAgg:
111
+ case T_JsonArrayAgg:
112
+ case T_JsonArrayQueryConstructor:
113
+ return true;
114
+ default:
115
+ return false;
116
+ }
117
+ }
@@ -1,4 +1,5 @@
1
1
  #include "pg_query.h"
2
+ #include "postgres_deparse.h"
2
3
  #include "xxhash/xxhash.h"
3
4
  #include <ruby.h>
4
5
 
@@ -6,12 +7,16 @@ void raise_ruby_parse_error(PgQueryProtobufParseResult result);
6
7
  void raise_ruby_normalize_error(PgQueryNormalizeResult result);
7
8
  void raise_ruby_fingerprint_error(PgQueryFingerprintResult result);
8
9
  void raise_ruby_scan_error(PgQueryScanResult result);
10
+ void raise_ruby_split_error(PgQuerySplitResult result);
9
11
 
10
12
  VALUE pg_query_ruby_parse_protobuf(VALUE self, VALUE input);
11
13
  VALUE pg_query_ruby_deparse_protobuf(VALUE self, VALUE input);
14
+ VALUE pg_query_ruby_deparse_protobuf_opts(VALUE self, VALUE input, VALUE pretty_print, VALUE comments, VALUE indent_size, VALUE max_line_length, VALUE trailing_newline, VALUE commas_start_of_line);
15
+ VALUE pg_query_ruby_deparse_comments_for_query(VALUE self, VALUE input);
12
16
  VALUE pg_query_ruby_normalize(VALUE self, VALUE input);
13
17
  VALUE pg_query_ruby_fingerprint(VALUE self, VALUE input);
14
18
  VALUE pg_query_ruby_scan(VALUE self, VALUE input);
19
+ VALUE pg_query_ruby_split_with_parser(VALUE self, VALUE input);
15
20
  VALUE pg_query_ruby_hash_xxh3_64(VALUE self, VALUE input, VALUE seed);
16
21
 
17
22
  __attribute__((visibility ("default"))) void Init_pg_query(void)
@@ -22,9 +27,12 @@ __attribute__((visibility ("default"))) void Init_pg_query(void)
22
27
 
23
28
  rb_define_singleton_method(cPgQuery, "parse_protobuf", pg_query_ruby_parse_protobuf, 1);
24
29
  rb_define_singleton_method(cPgQuery, "deparse_protobuf", pg_query_ruby_deparse_protobuf, 1);
30
+ rb_define_singleton_method(cPgQuery, "deparse_protobuf_opts", pg_query_ruby_deparse_protobuf_opts, 7);
31
+ rb_define_singleton_method(cPgQuery, "deparse_comments_for_query", pg_query_ruby_deparse_comments_for_query, 1);
25
32
  rb_define_singleton_method(cPgQuery, "normalize", pg_query_ruby_normalize, 1);
26
33
  rb_define_singleton_method(cPgQuery, "fingerprint", pg_query_ruby_fingerprint, 1);
27
34
  rb_define_singleton_method(cPgQuery, "_raw_scan", pg_query_ruby_scan, 1);
35
+ rb_define_singleton_method(cPgQuery, "_raw_split_with_parser", pg_query_ruby_split_with_parser, 1);
28
36
  rb_define_singleton_method(cPgQuery, "hash_xxh3_64", pg_query_ruby_hash_xxh3_64, 2);
29
37
  rb_define_const(cPgQuery, "PG_VERSION", rb_str_new2(PG_VERSION));
30
38
  rb_define_const(cPgQuery, "PG_MAJORVERSION", rb_str_new2(PG_MAJORVERSION));
@@ -67,6 +75,24 @@ void raise_ruby_deparse_error(PgQueryDeparseResult result)
67
75
  rb_exc_raise(rb_class_new_instance(4, args, cParseError));
68
76
  }
69
77
 
78
+ void raise_ruby_deparse_comments_error(PgQueryDeparseCommentsResult result)
79
+ {
80
+ VALUE cPgQuery, cParseError;
81
+ VALUE args[4];
82
+
83
+ cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
84
+ cParseError = rb_const_get_at(cPgQuery, rb_intern("ParseError"));
85
+
86
+ args[0] = rb_str_new2(result.error->message);
87
+ args[1] = rb_str_new2(result.error->filename);
88
+ args[2] = INT2NUM(result.error->lineno);
89
+ args[3] = INT2NUM(result.error->cursorpos);
90
+
91
+ pg_query_free_deparse_comments_result(result);
92
+
93
+ rb_exc_raise(rb_class_new_instance(4, args, cParseError));
94
+ }
95
+
70
96
  void raise_ruby_normalize_error(PgQueryNormalizeResult result)
71
97
  {
72
98
  VALUE cPgQuery, cParseError;
@@ -121,6 +147,24 @@ void raise_ruby_scan_error(PgQueryScanResult result)
121
147
  rb_exc_raise(rb_class_new_instance(4, args, cScanError));
122
148
  }
123
149
 
150
+ void raise_ruby_split_error(PgQuerySplitResult result)
151
+ {
152
+ VALUE cPgQuery, cSplitError;
153
+ VALUE args[4];
154
+
155
+ cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
156
+ cSplitError = rb_const_get_at(cPgQuery, rb_intern("SplitError"));
157
+
158
+ args[0] = rb_str_new2(result.error->message);
159
+ args[1] = rb_str_new2(result.error->filename);
160
+ args[2] = INT2NUM(result.error->lineno);
161
+ args[3] = INT2NUM(result.error->cursorpos);
162
+
163
+ pg_query_free_split_result(result);
164
+
165
+ rb_exc_raise(rb_class_new_instance(4, args, cSplitError));
166
+ }
167
+
124
168
  VALUE pg_query_ruby_parse_protobuf(VALUE self, VALUE input)
125
169
  {
126
170
  Check_Type(input, T_STRING);
@@ -161,6 +205,83 @@ VALUE pg_query_ruby_deparse_protobuf(VALUE self, VALUE input)
161
205
  return output;
162
206
  }
163
207
 
208
+ VALUE pg_query_ruby_deparse_protobuf_opts(VALUE self, VALUE input, VALUE pretty_print, VALUE comments, VALUE indent_size, VALUE max_line_length, VALUE trailing_newline, VALUE commas_start_of_line)
209
+ {
210
+ Check_Type(input, T_STRING);
211
+ Check_Type(comments, T_ARRAY);
212
+ Check_Type(indent_size, T_FIXNUM);
213
+ Check_Type(max_line_length, T_FIXNUM);
214
+
215
+ VALUE output;
216
+ PgQueryProtobuf pbuf = {0};
217
+ PgQueryDeparseResult result = {0};
218
+ PostgresDeparseOpts deparse_opts = {0};
219
+ deparse_opts.pretty_print = RTEST(pretty_print);
220
+ deparse_opts.indent_size = NUM2INT(indent_size);
221
+ deparse_opts.max_line_length = NUM2INT(max_line_length);
222
+ deparse_opts.trailing_newline = RTEST(trailing_newline);
223
+ deparse_opts.commas_start_of_line = RTEST(commas_start_of_line);
224
+ deparse_opts.comments = malloc(RARRAY_LEN(comments) * sizeof(PostgresDeparseComment *));
225
+ deparse_opts.comment_count = RARRAY_LEN(comments);
226
+ VALUE *comments_arr = RARRAY_PTR(comments);
227
+
228
+ for (int i = 0; i < RARRAY_LEN(comments); i++)
229
+ {
230
+ PostgresDeparseComment* comment = malloc(sizeof(PostgresDeparseComment));
231
+ VALUE str_ref = rb_ivar_get(comments_arr[i], rb_intern("@str"));
232
+ comment->match_location = NUM2INT(rb_ivar_get(comments_arr[i], rb_intern("@match_location")));
233
+ comment->newlines_before_comment = NUM2INT(rb_ivar_get(comments_arr[i], rb_intern("@newlines_before_comment")));
234
+ comment->newlines_after_comment = NUM2INT(rb_ivar_get(comments_arr[i], rb_intern("@newlines_after_comment")));
235
+ comment->str = StringValueCStr(str_ref);
236
+
237
+ deparse_opts.comments[i] = comment;
238
+ }
239
+
240
+ pbuf.data = StringValuePtr(input);
241
+ pbuf.len = RSTRING_LEN(input);
242
+ result = pg_query_deparse_protobuf_opts(pbuf, deparse_opts);
243
+
244
+ if (result.error) raise_ruby_deparse_error(result);
245
+
246
+ output = rb_str_new2(result.query);
247
+
248
+ pg_query_free_deparse_result(result);
249
+ for (int i = 0; i < deparse_opts.comment_count; i++)
250
+ free(deparse_opts.comments[i]);
251
+ free(deparse_opts.comments);
252
+
253
+ return output;
254
+ }
255
+
256
+ VALUE pg_query_ruby_deparse_comments_for_query(VALUE self, VALUE input)
257
+ {
258
+ Check_Type(input, T_STRING);
259
+
260
+ VALUE cPgQuery, cDeparseComment;
261
+
262
+ cPgQuery = rb_const_get(rb_cObject, rb_intern("PgQuery"));
263
+ cDeparseComment = rb_const_get_at(cPgQuery, rb_intern("DeparseComment"));
264
+
265
+ VALUE output = rb_ary_new();
266
+ PgQueryDeparseCommentsResult result = pg_query_deparse_comments_for_query(StringValueCStr(input));
267
+ if (result.error) raise_ruby_deparse_comments_error(result);
268
+
269
+ for (int i = 0; i < result.comment_count; i++)
270
+ {
271
+ PostgresDeparseComment* comment = result.comments[i];
272
+ VALUE c = rb_class_new_instance(0, NULL, cDeparseComment);
273
+ rb_ivar_set(c, rb_intern("@str"), rb_str_new2(comment->str));
274
+ rb_ivar_set(c, rb_intern("@match_location"), INT2NUM(comment->match_location));
275
+ rb_ivar_set(c, rb_intern("@newlines_before_comment"), INT2NUM(comment->newlines_before_comment));
276
+ rb_ivar_set(c, rb_intern("@newlines_after_comment"), INT2NUM(comment->newlines_after_comment));
277
+ rb_ary_push(output, c);
278
+ }
279
+
280
+ pg_query_free_deparse_comments_result(result);
281
+
282
+ return output;
283
+ }
284
+
164
285
  VALUE pg_query_ruby_normalize(VALUE self, VALUE input)
165
286
  {
166
287
  Check_Type(input, T_STRING);
@@ -216,6 +337,35 @@ VALUE pg_query_ruby_scan(VALUE self, VALUE input)
216
337
  return output;
217
338
  }
218
339
 
340
+ VALUE pg_query_ruby_split_with_parser(VALUE self, VALUE input)
341
+ {
342
+ Check_Type(input, T_STRING);
343
+
344
+ VALUE output;
345
+ VALUE stmts;
346
+ PgQuerySplitResult result = pg_query_split_with_parser(StringValueCStr(input));
347
+
348
+ if (result.error) raise_ruby_split_error(result);
349
+
350
+ output = rb_ary_new();
351
+ stmts = rb_ary_new();
352
+
353
+ for (int i = 0; i < result.n_stmts; i++)
354
+ {
355
+ VALUE stmt = rb_ary_new();
356
+ rb_ary_push(stmt, INT2NUM(result.stmts[i]->stmt_location));
357
+ rb_ary_push(stmt, INT2NUM(result.stmts[i]->stmt_len));
358
+ rb_ary_push(stmts, stmt);
359
+ }
360
+
361
+ rb_ary_push(output, stmts);
362
+ rb_ary_push(output, rb_str_new2(result.stderr_buffer));
363
+
364
+ pg_query_free_split_result(result);
365
+
366
+ return output;
367
+ }
368
+
219
369
  VALUE pg_query_ruby_hash_xxh3_64(VALUE self, VALUE input, VALUE seed)
220
370
  {
221
371
  Check_Type(input, T_STRING);