PostgreSQL json jsonb 支持的value数据类型,如何构造一个jsonb

Postgres2015全国用户大会将于11月20至21日在北京丽亭华苑酒店召开。本次大会嘉宾阵容强大,国内顶级PostgreSQL数据库专家将悉数到场,并特邀欧洲、俄罗斯、日本、美国等国家和地区的数据库方面专家助阵:

  • Postgres-XC项目的发起人铃木市一(SUZUKI Koichi)
  • Postgres-XL的项目发起人Mason Sharp
  • pgpool的作者石井达夫(Tatsuo Ishii)
  • PG-Strom的作者海外浩平(Kaigai Kohei)
  • Greenplum研发总监姚延栋
  • 周正中(德哥), PostgreSQL中国用户会创始人之一
  • 汪洋,平安科技数据库技术部经理
  • ……
 
  • 2015年度PG大象会报名地址:http://postgres2015.eventdove.com/
  • PostgreSQL中国社区: http://postgres.cn/
  • PostgreSQL专业1群: 3336901(已满)
  • PostgreSQL专业2群: 100910388
  • PostgreSQL专业3群: 150657323


PostgreSQL 支持JSON已久,那么JSON的value中支持哪些类型呢?
目前支持string, numeric, boolean (true, false), null。 区分大小写哦。

/*
 *      Recursive Descent parse routines. There is one for each structural
 *      element in a json document:
 *        - scalar (string, number, true, false, null)
 *        - array  ( [ ] )
 *        - array element
 *        - object ( { } )
 *        - object field
 */
static inline void
parse_scalar(JsonLexContext *lex, JsonSemAction *sem)
{
        char       *val = NULL;
        json_scalar_action sfunc = sem->scalar;
        char      **valaddr;
        JsonTokenType tok = lex_peek(lex);

        valaddr = sfunc == NULL ? NULL : &val;

        /* a scalar must be a string, a number, true, false, or null */
        switch (tok)
        {
                case JSON_TOKEN_TRUE:
                        lex_accept(lex, JSON_TOKEN_TRUE, valaddr);
                        break;
                case JSON_TOKEN_FALSE:
                        lex_accept(lex, JSON_TOKEN_FALSE, valaddr);
                        break;
                case JSON_TOKEN_NULL:
                        lex_accept(lex, JSON_TOKEN_NULL, valaddr);
                        break;
                case JSON_TOKEN_NUMBER:
                        lex_accept(lex, JSON_TOKEN_NUMBER, valaddr);
                        break;
                case JSON_TOKEN_STRING:
                        lex_accept(lex, JSON_TOKEN_STRING, valaddr);
                        break;
                default:
                        report_parse_error(JSON_PARSE_VALUE, lex);
        }

        if (sfunc != NULL)
                (*sfunc) (sem->semstate, val, tok);
}

例子:

postgres=# select jsonb '{"a": TRUE}';
ERROR:  invalid input syntax for type json
LINE 1: select jsonb '{"a": TRUE}';
                     ^
DETAIL:  Token "TRUE" is invalid.
CONTEXT:  JSON data, line 1: {"a": TRUE...
postgres=# select jsonb '{"a": true}';
    jsonb    
-------------
 {"a": true}
(1 row)

postgres=# select jsonb '{"a": false}';
    jsonb     
--------------
 {"a": false}
(1 row)

postgres=# select jsonb '{"a": NULL}';
ERROR:  invalid input syntax for type json
LINE 1: select jsonb '{"a": NULL}';
                     ^
DETAIL:  Token "NULL" is invalid.
CONTEXT:  JSON data, line 1: {"a": NULL...
postgres=# select jsonb '{"a": null}';
    jsonb    
-------------
 {"a": null}
(1 row)

postgres=# select jsonb '{"a": 10.001}';
     jsonb     
---------------
 {"a": 10.001}
(1 row)

postgres=# select jsonb '{"a": "10.001"}';
      jsonb      
-----------------
 {"a": "10.001"}
(1 row)


src/backend/utils/adt/json.c
解析json,jsonb时,定义的token类型,这个和JSONB里面能存什么类型没有关系:

/*
 * The context of the parser is maintained by the recursive descent
 * mechanism, but is passed explicitly to the error reporting routine
 * for better diagnostics.
 */
typedef enum                                    /* contexts of JSON parser */
{
        JSON_PARSE_VALUE,                       /* expecting a value */
        JSON_PARSE_STRING,                      /* expecting a string (for a field name) */
        JSON_PARSE_ARRAY_START,         /* saw '[', expecting value or ']' */
        JSON_PARSE_ARRAY_NEXT,          /* saw array element, expecting ',' or ']' */
        JSON_PARSE_OBJECT_START,        /* saw '{', expecting label or '}' */
        JSON_PARSE_OBJECT_LABEL,        /* saw object label, expecting ':' */
        JSON_PARSE_OBJECT_NEXT         /* saw object value, expecting ',' or '}' */
        JSON_PARSE_OBJECT_COMMA,        /* saw object ',', expecting next label */
        JSON_PARSE_END                          /* saw the end of a document, expect nothing */
} JsonParseContext;


实际上jsonb里没有类型的概念,你前面看到的TOKEN实际上也就是字符串匹配而已,你可以理解为jsonb内部就是一个有一定规则的字符串。
那么我们怎么来构造一个JSONB类型,并且在使用时可以很好的处理里面的数据类型呢?
这里需要用到PostgreSQL的输入输出函数,一般每个类型都会有对应的in和out函数,例如:

 pg_catalog | anyrange_in                     | anyrange         | cstring, oid, integer                                          | normal
 pg_catalog | anyrange_out                    | cstring          | anyrange                                                       | normal
 pg_catalog | timestamptz_out               | cstring                     | timestamp with time zone                                                                                  | normal
 pg_catalog | timestamptz_in                | timestamp with time zone    | cstring, oid, integer                                                                                     | normal


/* timestamptz_in()
 * Convert a string to internal form.
 */
Datum
timestamptz_in(PG_FUNCTION_ARGS)
{
        char       *str = PG_GETARG_CSTRING(0);

#ifdef NOT_USED
        Oid                     typelem = PG_GETARG_OID(1);
#endif
        int32           typmod = PG_GETARG_INT32(2);


postgres=# select timestamptz_out(now());
        timestamptz_out        
-------------------------------
 2015-09-24 19:44:16.076233+08
(1 row)

postgres=# select oid,* from pg_type where typname='timestamptz';
-[ RECORD 1 ]--+---------------------
oid            | 1184
typname        | timestamptz
typnamespace   | 11
typowner       | 10
typlen         | 8
typbyval       | t
typtype        | b
typcategory    | D
typispreferred | t
typisdefined   | t
typdelim       | ,
typrelid       | 0
typelem        | 0
typarray       | 1185
typinput       | timestamptz_in
typoutput      | timestamptz_out
typreceive     | timestamptz_recv
typsend        | timestamptz_send
typmodin       | timestamptztypmodin
typmodout      | timestamptztypmodout
typanalyze     | -
typalign       | d
typstorage     | p
typnotnull     | f
typbasetype    | 0
typtypmod      | -1
typndims       | 0
typcollation   | 0
typdefaultbin  | 
typdefault     | 
typacl         | 


typmode表示精度

postgres=# select timestamptz_in('2015-09-24 19:44:16.076233+08',1184,1);
-[ RECORD 1 ]--+-------------------------
timestamptz_in | 2015-09-24 19:44:16.1+08
postgres=# select timestamptz_in('2015-09-24 19:44:16.076233+08',1184,6);
-[ RECORD 1 ]--+------------------------------
timestamptz_in | 2015-09-24 19:44:16.076233+08


使用format格式来构造jsonb类型是比较靠谱的。
代码见varlena.c

/*
 * Returns a formatted string
 */
Datum
text_format(PG_FUNCTION_ARGS)
{
......

例子
构造一个jsonb的cstring

postgres=# select format('{"K": "%s"}', int8range(1,10));
     format      
-----------------
 {"K": "[1,10)"}
(1 row)


使用jsonb_in把cstring转换为JSONB

postgres=# select jsonb_in( format('{"K": "%s"}', int8range(1,10))::cstring );
    jsonb_in     
-----------------
 {"K": "[1,10)"}
(1 row)

取出K的element

postgres=# select jsonb_in( format('{"K": "%s"}', int8range(1,10))::cstring ) ->> 'K';
 ?column? 
----------
 [1,10)
(1 row)

将element转换回去

postgres=# select (jsonb_in( format('{"K": "%s"}', int8range(1,10))::cstring ) ->> 'K') :: int8range;
 int8range 
-----------
 [1,10)
(1 row)


再举个例子

postgres=# select format('{"K": "%s"}', '你好'::bytea);
         format          
-------------------------
 {"K": "\xe4bda0e5a5bd"}
(1 row)
postgres=# select convert_from('\xe4bda0e5a5bd', 'utf8');
 convert_from 
--------------
 你好
(1 row)
postgres=# \set VERBOSITY verbose
postgres=# select jsonb_in( format('{"K": "%s"}', '你好'::bytea)::cstring );
ERROR:  22P02: invalid input syntax for type json
DETAIL:  Escape sequence "\x" is invalid.
CONTEXT:  JSON data, line 1: {"K": "\x...
LOCATION:  json_lex_string, json.c:890

非常遗憾,JSONB会处理escape,而\x是不合法的,报错了。这块代码可以改一改。
代码如下:

/*
 * The next token in the input stream is known to be a string; lex it.
 */
static inline void
json_lex_string(JsonLexContext *lex)
{
......
                                switch (*s)
                                {
                                        case '"':
                                        case '\\':
                                        case '/':
                                                appendStringInfoChar(lex->strval, *s);
                                                break;
                                        case 'b':
                                                appendStringInfoChar(lex->strval, '\b');
                                                break;
                                        case 'f':
                                                appendStringInfoChar(lex->strval, '\f');
                                                break;
                                        case 'n':
                                                appendStringInfoChar(lex->strval, '\n');
                                                break;
                                        case 'r':
                                                appendStringInfoChar(lex->strval, '\r');
                                                break;
                                        case 't':
                                                appendStringInfoChar(lex->strval, '\t');
                                                break;
                                        default:
                                                /* Not a valid string escape, so error out. */
                                                lex->token_terminator = s + pg_mblen(s);
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                                                                 errmsg("invalid input syntax for type json"),
                                                        errdetail("Escape sequence \"\\%s\" is invalid.",
                                                                          extract_mb_char(s)),
                                                                 report_json_context(lex)));
                                }


修改后,可以实现存储BYTEA,但是输出时还需要转换一下。

postgres=# select jsonb_in( format('{"K": "%s"}', '你好'::bytea)::cstring ) ->> 'K';
   ?column?   
--------------
 e4bda0e5a5bd
(1 row)

postgres=# select convert_from( byteain (('\x'||(jsonb_in( format('{"K": "%s"}', '你好'::bytea)::cstring ) ->> 'K'))::cstring), 'utf8'::name);
 convert_from 
--------------
 你好
(1 row)


用format来构造一个字符串,在里面使用%s填充需要的cstring,从而达到构造jsonb的目的。
这种方法同样适用构造JSONB中包含postgis的几何类型。

[参考]
2. src/backend/utils/adt/json.c

/*
 * Lex one token from the input stream.
 */
static inline void
json_lex(JsonLexContext *lex)
{
        char       *s;
        int                     len;

        /* Skip leading whitespace. */
        s = lex->token_terminator;
        len = s - lex->input;
        while (len < lex->input_length &&
                   (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r'))
        {
                if (*s == '\n')
                        ++lex->line_number;
                ++s;
                ++len;
        }
        lex->token_start = s;

        /* Determine token type. */
        if (len >= lex->input_length)
        {
                lex->token_start = NULL;
                lex->prev_token_terminator = lex->token_terminator;
                lex->token_terminator = s;
                lex->token_type = JSON_TOKEN_END;
        }
        else
                switch (*s)
                {
                                /* Single-character token, some kind of punctuation mark. */
                        case '{':
                                lex->prev_token_terminator = lex->token_terminator;
                                lex->token_terminator = s + 1;
                                lex->token_type = JSON_TOKEN_OBJECT_START;
                                lex->token_terminator = s + 1;
                                lex->token_type = JSON_TOKEN_OBJECT_START;
                                break;
                        case '}':
                                lex->prev_token_terminator = lex->token_terminator;
                                lex->token_terminator = s + 1;
                                lex->token_type = JSON_TOKEN_OBJECT_END;
                                break;
                        case '[':
                                lex->prev_token_terminator = lex->token_terminator;
                                lex->token_terminator = s + 1;
                                lex->token_type = JSON_TOKEN_ARRAY_START;
                                break;
                        case ']':
                                lex->prev_token_terminator = lex->token_terminator;
                                lex->token_terminator = s + 1;
                                lex->token_type = JSON_TOKEN_ARRAY_END;
                                break;
                        case ',':
                                lex->prev_token_terminator = lex->token_terminator;
                                lex->token_terminator = s + 1;
                                lex->token_type = JSON_TOKEN_COMMA;
                                break;
                        case ':':
                                lex->prev_token_terminator = lex->token_terminator;
                                lex->token_terminator = s + 1;
                                lex->token_type = JSON_TOKEN_COLON;
                                break;
                        case '"':
                                /* string */
                                json_lex_string(lex);
                                lex->token_type = JSON_TOKEN_STRING;
                                break;
                        case '-':
                                /* Negative number. */
                                json_lex_number(lex, s + 1, NULL);
                                lex->token_type = JSON_TOKEN_NUMBER;
                                break;
                        case '0':
                        case '1':
                        case '2':
                        case '3':
                        case '4':
                        case '5':
                        case '6':
                        case '7':
                        case '8':
                        case '9':
                                /* Positive number. */
                                json_lex_number(lex, s, NULL);
                                lex->token_type = JSON_TOKEN_NUMBER;
                                break;
                        default:
                                {
                                        char       *p;
                                {
                                        char       *p;

                                        /*
                                         * We're not dealing with a string, number, legal
                                         * punctuation mark, or end of string.  The only legal
                                         * tokens we might find here are true, false, and null,
                                         * but for error reporting purposes we scan until we see a
                                         * non-alphanumeric character.  That way, we can report
                                         * the whole word as an unexpected token, rather than just
                                         * some unintuitive prefix thereof.
                                         */
                                        for (p = s; p - s < lex->input_length - len && JSON_ALPHANUMERIC_CHAR(*p); p++)
                                                 /* skip */ ;

                                        /*
                                         * We got some sort of unexpected punctuation or an
                                         * otherwise unexpected character, so just complain about
                                         * that one character.
                                         */
                                        if (p == s)
                                        {
                                                lex->prev_token_terminator = lex->token_terminator;
                                                lex->token_terminator = s + 1;
                                                report_invalid_token(lex);
                                        }

                                        /*
                                         * We've got a real alphanumeric token here.  If it
                                         * happens to be true, false, or null, all is well.  If
                                         * not, error out.
                                         */
                                        lex->prev_token_terminator = lex->token_terminator;
                                        lex->token_terminator = p;
                                        if (p - s == 4)
                                        {
                                                if (memcmp(s, "true", 4) == 0)
                                                        lex->token_type = JSON_TOKEN_TRUE;
                                                else if (memcmp(s, "null", 4) == 0)
                                                        lex->token_type = JSON_TOKEN_NULL;
                                                else
                                                        report_invalid_token(lex);
                                        }
                                        else if (p - s == 5 && memcmp(s, "false", 5) == 0)
                                                lex->token_type = JSON_TOKEN_FALSE;
                                        else
                                                report_invalid_token(lex);

                                }
                }                                               /* end of switch */
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值