Commits

Andrew Dunstan committed 77c70ab

working populate_record

Comments (0)

Files changed (3)

src/backend/utils/adt/jsonfuncs.c

 static void unnest_array_element_end(void *state, bool isnull);
 static void unnest_scalar(void *state, char *token, JsonTokenType tokentype);
 
+/* turn a json object into a hash table */
+static HTAB *get_json_object_as_hash(char * jsonstr, char *funcname);
+/* semantic action functions for het_json_object_as_hash */
+static void hash_object_start(void *state);
+static void hash_object_field_start(void *state, char *fname, bool isnull);
+static void hash_object_field_end(void *state, char *fname, bool isnull);
+static void hash_array_start(void *state);
+static void hash_scalar(void *state, char *token, JsonTokenType tokentype);
+
+
+
 /* search type classification for json_get* functions */
 typedef enum
 {
 	char  *result_start;
 } unnestState, *UnnestState;
 
+typedef struct
+{
+	JsonLexContext *lex;
+	int			lex_level;
+    HTAB        *hash;
+    char        *saved_scalar;
+	char  *function_name;
+} jhashState, *JHashState;
+
+/* used to build the hashtable used in populate-record */
+typedef struct 
+{
+	char fname[NAMEDATALEN];
+	char *val;
+	bool isnull;
+} jsonHashEntry, *JsonHashEntry;
+
+/* these two are stolen from hstore / record_out, used in populate_record */
+typedef struct ColumnIOData
+{
+	Oid			column_type;
+	Oid			typiofunc;
+	Oid			typioparam;
+	FmgrInfo	proc;
+} ColumnIOData;
+
+typedef struct RecordIOData
+{
+	Oid			record_type;
+	int32		record_typmod;
+	int			ncolumns;
+	ColumnIOData columns[1];	/* VARIABLE LENGTH ARRAY */
+} RecordIOData;
+
 
 PG_FUNCTION_INFO_V1(json_object_keys);
 
 				 errmsg("cannot call json_unnest on a scalar")));
 }
 
-typedef struct 
-{
-	char *string;
-	bool isnull;
-} jsonHashVal, *JsonHashVal;
-
-typedef struct 
-{
-	char fname[NAMEDATALEN];
-	JsonHashVal value;
-} jsonHashEntry, *JsonHashEntry;
-
-typedef struct ColumnIOData
-{
-	Oid			column_type;
-	Oid			typiofunc;
-	Oid			typioparam;
-	FmgrInfo	proc;
-} ColumnIOData;
-
-typedef struct RecordIOData
-{
-	Oid			record_type;
-	int32		record_typmod;
-	int			ncolumns;
-	ColumnIOData columns[1];	/* VARIABLE LENGTH ARRAY */
-} RecordIOData;
-
 static HTAB *
-get_json_object_as_hash(char * jsonstr)
+get_json_object_as_hash(char * jsonstr, char *funcname)
 {
 
 	HASHCTL     ctl;
 	HTAB       *tab;
+	JHashState	   state;
+	JsonLexContext lex;
+	JsonSemAction  sem;
+
 
 	memset(&ctl, 0, sizeof(ctl));
     ctl.keysize = NAMEDATALEN;
 					  &ctl,
 					  HASH_ELEM);
 
+	state = palloc0(sizeof(jhashState));
+	sem = palloc0(sizeof(jsonSemAction));
+
+	/* palloc0 does this for us */
+#if 0	 
+	state->lex_level = 0;
+#endif
+
+	state->function_name = funcname;
+	state->hash = tab;
+	state->lex = &lex;
+
+	sem->semstate = (void *) state;
+	sem->object_start = hash_object_start;
+	sem->array_start = hash_array_start;
+	sem->scalar = hash_scalar;
+	sem->object_field_start = hash_object_field_start;
+	sem->object_field_end = hash_object_field_end;
+
+	/* Set up lexing context. */
+	lex.input = jsonstr;
+	lex.token_terminator = lex.input;
+	lex.line_number = 1;
+	lex.line_start = jsonstr;
+	lex.strval = makeStringInfo();
+
+	pg_parse_json(&lex, sem);
+
 	return tab;
 }
 
 	Oid			argtype = get_fn_expr_argtype(fcinfo->flinfo, 0);
 	text	   *json = PG_GETARG_TEXT_P(1);
 	char	   *jsonstr = text_to_cstring(json);
-/*
-	HStore	   *hs;
-	HEntry	   *entries;
-	char	   *ptr;
-*/
-
 	HTAB       *json_hash;
 	HeapTupleHeader rec;
 	Oid			tupType;
 	Datum	   *values;
 	bool	   *nulls;
 	char       fname[NAMEDATALEN];
-	JsonHashVal hashval;
+	JsonHashEntry hashentry;
 	
 
 	if (!type_is_rowtype(argtype))
 		tupTypmod = HeapTupleHeaderGetTypMod(rec);
 	}
 
-/*
-	hs = PG_GETARG_HS(1);
-	entries = ARRPTR(hs);
-	ptr = STRPTR(hs);
-*/
-
-	json_hash = get_json_object_as_hash(jsonstr);
+	json_hash = get_json_object_as_hash(jsonstr,"json_populate_record");
 
 	/*
 	 * if the input hstore is empty, we can only skip the rest if we were
 	 * passed in a non-null record, since otherwise there may be issues with
 	 * domain nulls.
 	 */
-/*
-	if (HS_COUNT(hs) == 0 && rec)
-		PG_RETURN_POINTER(rec);
-*/
-
 	if (hash_get_num_entries(json_hash) == 0 && rec)
 		PG_RETURN_POINTER(rec);
 		
 		ColumnIOData *column_info = &my_extra->columns[i];
 		Oid			column_type = tupdesc->attrs[i]->atttypid;
 		char	   *value;
-/*		
-		int			idx;
-		int			vallen;
-*/
 
 		/* Ignore dropped columns in datatype */
 		if (tupdesc->attrs[i]->attisdropped)
 			continue;
 		}
 
-/*
-		idx = hstoreFindKey(hs, 0,
-							NameStr(tupdesc->attrs[i]->attname),
-							strlen(NameStr(tupdesc->attrs[i]->attname)));
-*/
 		memset(fname, 0, NAMEDATALEN);
 		strncpy(fname, NameStr(tupdesc->attrs[i]->attname), NAMEDATALEN);
-		hashval = hash_search(json_hash, fname, HASH_FIND, NULL);
+		hashentry = hash_search(json_hash, fname, HASH_FIND, NULL);
 
 		/*
 		 * we can't just skip here if the key wasn't found since we might have
 		 * then every field which we don't populate needs to be run through
 		 * the input function just in case it's a domain type.
 		 */
-/*
-		if (idx < 0 && rec)
-			continue;
-*/
-		if (hashval == NULL && rec)
+		if (hashentry == NULL && rec)
 			continue;
 
 		/*
 						  fcinfo->flinfo->fn_mcxt);
 			column_info->column_type = column_type;
 		}
-/*
-		if (idx < 0 || HS_VALISNULL(entries, idx))
-*/
-		if (hashval == NULL || hashval->isnull)
+		if (hashentry == NULL || hashentry->isnull)
 		{
 			/*
 			 * need InputFunctionCall to happen even for nulls, so that domain
 		}
 		else
 		{
-/*			
-			vallen = HS_VALLEN(entries, idx);
-			value = palloc(1 + vallen);
-			memcpy(value, HS_VAL(entries, ptr, idx), vallen);
-			value[vallen] = 0;
-*/
-			value = hashval->string;
+			value = hashentry->val;
 
 			values[i] = InputFunctionCall(&column_info->proc, value,
 										  column_info->typioparam,
 	PG_RETURN_DATUM(HeapTupleGetDatum(rettuple));
 }
 
+static void hash_object_start(void *state)
+{
+	JHashState	_state = (JHashState) state;
+
+	_state->lex_level++;
+}
+
+/*
+static void hash_object_end(void *state)
+{
+	JHashState	_state = (JHashState) state;
+
+	_state->lex_level--;
+}
+*/
+
+static void hash_object_field_start(void *state, char *fname, bool isnull)
+{
+	JHashState	_state = (JHashState) state;
+
+	if (_state->lex->token_type == JSON_TOKEN_ARRAY_START ||
+		_state->lex->token_type == JSON_TOKEN_OBJECT_START)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("cannot call %s on a nested object", _state->function_name)));
+	}
+}
+
+static void hash_object_field_end(void *state, char *fname, bool isnull)
+{
+	JHashState	_state = (JHashState) state;
+	JsonHashEntry hashentry;
+	bool          found;
+	char          name[NAMEDATALEN];
+
+	memset(name,0,NAMEDATALEN);
+	strncpy(name,fname, NAMEDATALEN);
+
+	hashentry = hash_search(_state->hash, name, HASH_ENTER, &found);
+
+	if (found)
+		elog(ERROR,"duplicate key");
+
+	hashentry->isnull = isnull;
+	hashentry->val = _state->saved_scalar;
+}
+
+static void hash_array_start(void *state)
+{
+	JHashState	_state = (JHashState) state;
+
+	if (_state->lex_level == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("cannot call %s on an array", _state->function_name)));
+}
+
+static void hash_scalar(void *state, char *token, JsonTokenType tokentype)
+{
+	JHashState	_state = (JHashState) state;
+
+	if (_state->lex_level == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				 errmsg("cannot call %s on an array", _state->function_name)));
+
+	_state->saved_scalar = token;
+}

src/test/regress/expected/json.out

  false
 (6 rows)
 
+-- populate_record
+create type jpop as (a text, b int, c timestamp);
+select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+   a    | b | c 
+--------+---+---
+ blurfl |   | 
+(1 row)
+
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;
+   a    | b |            c             
+--------+---+--------------------------
+ blurfl | 3 | Mon Dec 31 15:30:56 2012
+(1 row)
+

src/test/regress/sql/json.sql

 
 select json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]');
 select * from json_unnest('[1,true,[1,[2,3]],null,{"f1":1,"f2":[7,8,9]},false]') q;
+
+-- populate_record
+create type jpop as (a text, b int, c timestamp);
+
+select * from json_populate_record(null::jpop,'{"a":"blurfl","x":43.2}') q;
+select * from json_populate_record(row('x',3,'2012-12-31 15:30:56')::jpop,'{"a":"blurfl","x":43.2}') q;