PostgreSQL查询SQL语义分析(1)—解析查询对象addRangeTableEntry

本文主要介绍PG在执行查询时,对SQL的语义分析重写过程中的查询对象解析过程,处理的函数为addRangeTableEntry,分析查询对象信息。

一、源码解读

本函数是解析查询(包含增删改查操作)执行过程中涉及的查询对象(表、视图、子查询等)的信息。

每次调用只解析一个对象。

/* src/backend/parser/parse_relation.c */

/*
 * Add an entry for a relation to the pstate's range table (p_rtable).
 *
 * Note: formerly this checked for refname conflicts, but that's wrong.
 * Caller is responsible for checking for conflicts in the appropriate scope.
 */
RangeTblEntry *
addRangeTableEntry(ParseState *pstate,  // 查询信息
                   RangeVar *relation,  // 本次分析的关系对象
                   Alias *alias,  // 别名
                   bool inh,  // 是否是被继承的表
                   bool inFromCl)  // 是否是from 语句中的对象
{
    RangeTblEntry *rte = makeNode(RangeTblEntry); // 创建一个表实例
    char       *refname = alias ? alias->aliasname : relation->relname;
    LOCKMODE    lockmode;
    Relation    rel; // 关系缓存实体

    Assert(pstate != NULL);

    rte->rtekind = RTE_RELATION;
    rte->alias = alias;

    /*
     * Get the rel's OID.  This access also ensures that we have an up-to-date
     * relcache entry for the rel.  Since this is typically the first access
     * to a rel in a statement, be careful to get the right access level
     * depending on whether we're doing SELECT FOR UPDATE/SHARE.
     */
    lockmode = isLockedRefname(pstate, refname) ? RowShareLock : AccessShareLock; // 根据查询分析,判断锁表类型
    rel = parserOpenTable(pstate, relation, lockmode); // 从缓存中获取关系缓存实体
    rte->relid = RelationGetRelid(rel);   // 获取关系缓存实体ID
    rte->relkind = rel->rd_rel->relkind; // 关系实体类型

    /*
     * Build the list of effective column names using user-supplied aliases
     * and/or actual column names.
     */
    rte->eref = makeAlias(refname, NIL);
    buildRelationAliases(rel->rd_att, alias, rte->eref); // 构建别名信息

    /*
     * Drop the rel refcount, but keep the access lock till end of transaction
     * so that the table can't be deleted or have its schema modified
     * underneath us.
     */
    heap_close(rel, NoLock);

    /*
     * Set flags and access permissions.
     *
     * The initial default on access checks is always check-for-READ-access,
     * which is the right thing for all except target tables.
     */
    rte->lateral = false;
    rte->inh = inh;
    rte->inFromCl = inFromCl;

    rte->requiredPerms = ACL_SELECT;
    rte->checkAsUser = InvalidOid;  /* not set-uid by default, either */
    rte->selectedCols = NULL;
    rte->insertedCols = NULL;
    rte->updatedCols = NULL;

    /*
     * Add completed RTE to pstate's range table list, but not to join list
     * nor namespace --- caller must do that if appropriate.
     */
    pstate->p_rtable = lappend(pstate->p_rtable, rte);  // 将表实例rte附加到pstate->p_rtable列表中

    return rte;
}

二、数据结构信息

  1. ParseState -SQL语义分析过程信息对象
/*
 * State information used during parse analysis
 *
 * parentParseState: NULL in a top-level ParseState.  When parsing a subquery,
 * links to current parse state of outer query.
 *
 * p_sourcetext: source string that generated the raw parsetree being
 * analyzed, or NULL if not available.  (The string is used only to
 * generate cursor positions in error messages: we need it to convert
 * byte-wise locations in parse structures to character-wise cursor
 * positions.)
 *
 * p_rtable: list of RTEs that will become the rangetable of the query.
 * Note that neither relname nor refname of these entries are necessarily
 * unique; searching the rtable by name is a bad idea.
 *
 * p_joinexprs: list of JoinExpr nodes associated with p_rtable entries.
 * This is one-for-one with p_rtable, but contains NULLs for non-join
 * RTEs, and may be shorter than p_rtable if the last RTE(s) aren't joins.
 *
 * p_joinlist: list of join items (RangeTblRef and JoinExpr nodes) that
 * will become the fromlist of the query's top-level FromExpr node.
 *
 * p_namespace: list of ParseNamespaceItems that represents the current
 * namespace for table and column lookup.  (The RTEs listed here may be just
 * a subset of the whole rtable.  See ParseNamespaceItem comments below.)
 *
 * p_lateral_active: true if we are currently parsing a LATERAL subexpression
 * of this parse level.  This makes p_lateral_only namespace items visible,
 * whereas they are not visible when p_lateral_active is FALSE.
 *
 * p_ctenamespace: list of CommonTableExprs (WITH items) that are visible
 * at the moment.  This is entirely different from p_namespace because a CTE
 * is not an RTE, rather "visibility" means you could make an RTE from it.
 *
 * p_future_ctes: list of CommonTableExprs (WITH items) that are not yet
 * visible due to scope rules.  This is used to help improve error messages.
 *
 * p_parent_cte: CommonTableExpr that immediately contains the current query,
 * if any.
 *
 * p_target_relation: target relation, if query is INSERT, UPDATE, or DELETE.
 *
 * p_target_rangetblentry: target relation's entry in the rtable list.
 *
 * p_is_insert: true to process assignment expressions like INSERT, false
 * to process them like UPDATE.  (Note this can change intra-statement, for
 * cases like INSERT ON CONFLICT UPDATE.)
 *
 * p_windowdefs: list of WindowDefs representing WINDOW and OVER clauses.
 * We collect these while transforming expressions and then transform them
 * afterwards (so that any resjunk tlist items needed for the sort/group
 * clauses end up at the end of the query tlist).  A WindowDef's location in
 * this list, counting from 1, is the winref number to use to reference it.
 *
 * p_expr_kind: kind of expression we're currently parsing, as per enum above;
 * EXPR_KIND_NONE when not in an expression.
 *
 * p_next_resno: next TargetEntry.resno to assign, starting from 1.
 *
 * p_multiassign_exprs: partially-processed MultiAssignRef source expressions.
 *
 * p_locking_clause: query's FOR UPDATE/FOR SHARE clause, if any.
 *
 * p_locked_from_parent: true if parent query level applies FOR UPDATE/SHARE
 * to this subquery as a whole.
 *
 * p_resolve_unknowns: resolve unknown-type SELECT output columns as type TEXT
 * (this is true by default).
 *
 * p_hasAggs, p_hasWindowFuncs, etc: true if we've found any of the indicated
 * constructs in the query.
 *
 * p_last_srf: the set-returning FuncExpr or OpExpr most recently found in
 * the query, or NULL if none.
 *
 * p_pre_columnref_hook, etc: optional parser hook functions for modifying the
 * interpretation of ColumnRefs and ParamRefs.
 *
 * p_ref_hook_state: passthrough state for the parser hook functions.
 */
struct ParseState
{
    struct ParseState *parentParseState;    /* stack link */
    const char *p_sourcetext;   /* 查询的脚本源码 source text, or NULL if not available */
    List       *p_rtable;       /* 查询设计的对象 range table so far */
    List       *p_joinexprs;    /*对象关联条件 JoinExprs for RTE_JOIN p_rtable entries */
    List       *p_joinlist;     /*对象关联列表 join items so far (will become FromExpr
                                 * node's fromlist) */
    List       *p_namespace;    /* currently-referenceable RTEs (List of
                                 * ParseNamespaceItem) */
    bool        p_lateral_active;   /* p_lateral_only items visible? */
    List       *p_ctenamespace; /* current namespace for common table exprs */
    List       *p_future_ctes;  /* common table exprs not yet in namespace */
    CommonTableExpr *p_parent_cte;  /* this query's containing CTE */
    Relation    p_target_relation;  /* INSERT/UPDATE/DELETE target rel */
    RangeTblEntry *p_target_rangetblentry;  /* target rel's RTE */
    bool        p_is_insert;    /* process assignment like INSERT not UPDATE */
    List       *p_windowdefs;   /* raw representations of window clauses */
    ParseExprKind p_expr_kind;  /* what kind of expression we're parsing */
    int         p_next_resno;   /* next targetlist resno to assign */
    List       *p_multiassign_exprs;    /* junk tlist entries for multiassign */
    List       *p_locking_clause;   /* 锁 raw FOR UPDATE/FOR SHARE info */
    bool        p_locked_from_parent;   /* 锁 parent has marked this subquery
                                         * with FOR UPDATE/FOR SHARE */
    bool        p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
                                     * type text */

    QueryEnvironment *p_queryEnv;   /* curr env, incl refs to enclosing env */

    /* Flags telling about things found in the query: */
    bool        p_hasAggs;
    bool        p_hasWindowFuncs;
    bool        p_hasTargetSRFs;
    bool        p_hasSubLinks;
    bool        p_hasModifyingCTE;

    Node       *p_last_srf;     /* most recent set-returning func/op found */

    /*
     * Optional hook functions for parser callbacks.  These are null unless
     * set up by the caller of make_parsestate.
     */
    PreParseColumnRefHook p_pre_columnref_hook;
    PostParseColumnRefHook p_post_columnref_hook;
    ParseParamRefHook p_paramref_hook;
    CoerceParamHook p_coerce_param_hook;
    void       *p_ref_hook_state;   /* common passthrough link for above */
};
  1. RangeVar -查询的对象
/*
 * RangeVar - range variable, used in FROM clauses 查询的对象
 *
 * Also used to represent table names in utility statements; there, the alias
 * field is not used, and inh tells whether to apply the operation
 * recursively to child tables.  In some contexts it is also useful to carry
 * a TEMP table indication here.
 */
typedef struct RangeVar
{
    NodeTag     type;
    char       *catalogname;    /* 编目/数据库名 the catalog (database) name, or NULL */
    char       *schemaname;     /* 模式表 the schema name, or NULL */
    char       *relname;        /* 对象名称 the relation/sequence name */
    bool        inh;            /* expand rel by inheritance? recursively act
                                 * on children? */
    char        relpersistence; /* see RELPERSISTENCE_* in pg_class.h */
    Alias      *alias;          /* 查询中的别名 table alias & optional column aliases */
    int         location;       /* token location, or -1 if unknown */
} RangeVar;
  1. Alias-查询对象化名
/*
 * Alias - 化名
 *    specifies an alias for a range variable; the alias might also
 *    specify renaming of columns within the table.
 *
 * Note: colnames is a list of Value nodes (always strings).  In Alias structs
 * associated with RTEs, there may be entries corresponding to dropped
 * columns; these are normally empty strings ("").  See parsenodes.h for info.
 */
typedef struct Alias
{
    NodeTag     type;
    char       *aliasname;      /* 化名 aliased rel name (never qualified) */
    List       *colnames;       /* 字段名列表 optional list of column aliases */
} Alias;
  1. RangeTblEntry - 查询树节点中的表实体对象
/* src/include/nodes/parsenodes.h */

/*--------------------
 * RangeTblEntry -
 *    A range table is a List of RangeTblEntry nodes.
 *
 *    A range table entry may represent a plain relation, a sub-select in
 *    FROM, or the result of a JOIN clause.  (Only explicit JOIN syntax
 *    produces an RTE, not the implicit join resulting from multiple FROM
 *    items.  This is because we only need the RTE to deal with SQL features
 *    like outer joins and join-output-column aliasing.)  Other special
 *    RTE types also exist, as indicated by RTEKind.
 *
 *    Note that we consider RTE_RELATION to cover anything that has a pg_class
 *    entry.  relkind distinguishes the sub-cases.
 *
 *    alias is an Alias node representing the AS alias-clause attached to the
 *    FROM expression, or NULL if no clause.
 *
 *    eref is the table reference name and column reference names (either
 *    real or aliases).  Note that system columns (OID etc) are not included
 *    in the column list.
 *    eref->aliasname is required to be present, and should generally be used
 *    to identify the RTE for error messages etc.
 *
 *    In RELATION RTEs, the colnames in both alias and eref are indexed by
 *    physical attribute number; this means there must be colname entries for
 *    dropped columns.  When building an RTE we insert empty strings ("") for
 *    dropped columns.  Note however that a stored rule may have nonempty
 *    colnames for columns dropped since the rule was created (and for that
 *    matter the colnames might be out of date due to column renamings).
 *    The same comments apply to FUNCTION RTEs when a function's return type
 *    is a named composite type.
 *
 *    In JOIN RTEs, the colnames in both alias and eref are one-to-one with
 *    joinaliasvars entries.  A JOIN RTE will omit columns of its inputs when
 *    those columns are known to be dropped at parse time.  Again, however,
 *    a stored rule might contain entries for columns dropped since the rule
 *    was created.  (This is only possible for columns not actually referenced
 *    in the rule.)  When loading a stored rule, we replace the joinaliasvars
 *    items for any such columns with null pointers.  (We can't simply delete
 *    them from the joinaliasvars list, because that would affect the attnums
 *    of Vars referencing the rest of the list.)
 *
 *    inh is true for relation references that should be expanded to include
 *    inheritance children, if the rel has any.  This *must* be false for
 *    RTEs other than RTE_RELATION entries.
 *
 *    inFromCl marks those range variables that are listed in the FROM clause.
 *    It's false for RTEs that are added to a query behind the scenes, such
 *    as the NEW and OLD variables for a rule, or the subqueries of a UNION.
 *    This flag is not used anymore during parsing, since the parser now uses
 *    a separate "namespace" data structure to control visibility, but it is
 *    needed by ruleutils.c to determine whether RTEs should be shown in
 *    decompiled queries.
 *
 *    requiredPerms and checkAsUser specify run-time access permissions
 *    checks to be performed at query startup.  The user must have *all*
 *    of the permissions that are OR'd together in requiredPerms (zero
 *    indicates no permissions checking).  If checkAsUser is not zero,
 *    then do the permissions checks using the access rights of that user,
 *    not the current effective user ID.  (This allows rules to act as
 *    setuid gateways.)  Permissions checks only apply to RELATION RTEs.
 *
 *    For SELECT/INSERT/UPDATE permissions, if the user doesn't have
 *    table-wide permissions then it is sufficient to have the permissions
 *    on all columns identified in selectedCols (for SELECT) and/or
 *    insertedCols and/or updatedCols (INSERT with ON CONFLICT DO UPDATE may
 *    have all 3).  selectedCols, insertedCols and updatedCols are bitmapsets,
 *    which cannot have negative integer members, so we subtract
 *    FirstLowInvalidHeapAttributeNumber from column numbers before storing
 *    them in these fields.  A whole-row Var reference is represented by
 *    setting the bit for InvalidAttrNumber.
 *
 *    securityQuals is a list of security barrier quals (boolean expressions),
 *    to be tested in the listed order before returning a row from the
 *    relation.  It is always NIL in parser output.  Entries are added by the
 *    rewriter to implement security-barrier views and/or row-level security.
 *    Note that the planner turns each boolean expression into an implicitly
 *    AND'ed sublist, as is its usual habit with qualification expressions.
 *--------------------
 */
typedef enum RTEKind   // 实体类型
{
    RTE_RELATION,               /* 引用 ordinary relation reference */
    RTE_SUBQUERY,               /* 子查询 subquery in FROM */
    RTE_JOIN,                   /* 链接 join */
    RTE_FUNCTION,               /* 函数 function in FROM */
    RTE_TABLEFUNC,              /* 表函数 TableFunc(.., column list) */
    RTE_VALUES,                 /* 值 VALUES (<exprlist>), (<exprlist>), ... */
    RTE_CTE,                    /* 公共表表达式 common table expr (WITH list element) */
    RTE_NAMEDTUPLESTORE         /* tuple存储  tuplestore, e.g. for AFTER triggers */
} RTEKind;

typedef struct RangeTblEntry
{
    NodeTag     type;

    RTEKind     rtekind;        /* see above */

    /*
     * XXX the fields applicable to only some rte kinds should be merged into
     * a union.  I didn't do this yet because the diffs would impact a lot of
     * code that is being actively worked on.  FIXME someday.
     */

    /*
     * Fields valid for a plain relation RTE (else zero):
     *
     * As a special case, RTE_NAMEDTUPLESTORE can also set relid to indicate
     * that the tuple format of the tuplestore is the same as the referenced
     * relation.  This allows plans referencing AFTER trigger transition
     * tables to be invalidated if the underlying table is altered.
     */
    Oid         relid;          /* OID of the relation */
    char        relkind;        /* relation kind (see pg_class.relkind) */
    struct TableSampleClause *tablesample;  /* sampling info, or NULL */

    /*
     * Fields valid for a subquery RTE (else NULL):
     */
    Query      *subquery;       /* 子查询 the sub-query */
    bool        security_barrier;   /* is from security_barrier view? */

    /*
     * Fields valid for a join RTE (else NULL/zero):
     *
     * joinaliasvars is a list of (usually) Vars corresponding to the columns
     * of the join result.  An alias Var referencing column K of the join
     * result can be replaced by the K'th element of joinaliasvars --- but to
     * simplify the task of reverse-listing aliases correctly, we do not do
     * that until planning time.  In detail: an element of joinaliasvars can
     * be a Var of one of the join's input relations, or such a Var with an
     * implicit coercion to the join's output column type, or a COALESCE
     * expression containing the two input column Vars (possibly coerced).
     * Within a Query loaded from a stored rule, it is also possible for
     * joinaliasvars items to be null pointers, which are placeholders for
     * (necessarily unreferenced) columns dropped since the rule was made.
     * Also, once planning begins, joinaliasvars items can be almost anything,
     * as a result of subquery-flattening substitutions.
     */
    JoinType    jointype;       /* type of join */
    List       *joinaliasvars;  /* list of alias-var expansions */

    /*
     * Fields valid for a function RTE (else NIL/zero):
     *
     * When funcordinality is true, the eref->colnames list includes an alias
     * for the ordinality column.  The ordinality column is otherwise
     * implicit, and must be accounted for "by hand" in places such as
     * expandRTE().
     */
    List       *functions;      /* 表函数列表 list of RangeTblFunction nodes */
    bool        funcordinality; /* is this called WITH ORDINALITY? */

    /*
     * Fields valid for a TableFunc RTE (else NULL):
     */
    TableFunc  *tablefunc;

    /*
     * Fields valid for a values RTE (else NIL):
     */
    List       *values_lists;   /* list of expression lists */

    /*
     * Fields valid for a CTE RTE (else NULL/zero):
     */
    char       *ctename;        /* name of the WITH list item */
    Index       ctelevelsup;    /* number of query levels up */
    bool        self_reference; /* is this a recursive self-reference? */

    /*
     * Fields valid for table functions, values, CTE and ENR RTEs (else NIL):
     *
     * We need these for CTE RTEs so that the types of self-referential
     * columns are well-defined.  For VALUES RTEs, storing these explicitly
     * saves having to re-determine the info by scanning the values_lists. For
     * ENRs, we store the types explicitly here (we could get the information
     * from the catalogs if 'relid' was supplied, but we'd still need these
     * for TupleDesc-based ENRs, so we might as well always store the type
     * info here).
     *
     * For ENRs only, we have to consider the possibility of dropped columns.
     * A dropped column is included in these lists, but it will have zeroes in
     * all three lists (as well as an empty-string entry in eref).  Testing
     * for zero coltype is the standard way to detect a dropped column.
     */
    List       *coltypes;       /* OID list of column type OIDs */
    List       *coltypmods;     /* integer list of column typmods */
    List       *colcollations;  /* OID list of column collation OIDs */

    /*
     * Fields valid for ENR RTEs (else NULL/zero):
     */
    char       *enrname;        /* name of ephemeral named relation */
    double      enrtuples;      /* estimated or actual from caller */

    /*
     * Fields valid in all RTEs:
     */
    Alias      *alias;          /* 别名 user-written alias clause, if any */
    Alias      *eref;           /* expanded reference names */
    bool        lateral;        /* subquery, function, or values is LATERAL? */
    bool        inh;            /* inheritance requested? */
    bool        inFromCl;       /* present in FROM clause? */
    AclMode     requiredPerms;  /* bitmask of required access permissions */
    Oid         checkAsUser;    /* 对象ID if valid, check access as this role */
    Bitmapset  *selectedCols;   /* columns needing SELECT permission */
    Bitmapset  *insertedCols;   /* columns needing INSERT permission */
    Bitmapset  *updatedCols;    /* columns needing UPDATE permission */
    List       *securityQuals;  /* security barrier quals to apply, if any */
} RangeTblEntry;
  1. Relation - 关系缓存实体 的内容
    里面主要缓存了各种描述、主键、外键、触发器、索引、约束、对象锁...等对象的所有信息
/* src/include/utils/rel.h */

/*
 * Here are the contents of a relation cache entry.
 */

typedef struct RelationData
{
    RelFileNode rd_node;        /* relation physical identifier */
    /* use "struct" here to avoid needing to include smgr.h: */
    struct SMgrRelationData *rd_smgr;   /* cached file handle, or NULL */
    int         rd_refcnt;      /* reference count */
    BackendId   rd_backend;     /* owning backend id, if temporary relation */
    bool        rd_islocaltemp; /* rel is a temp rel of this session */
    bool        rd_isnailed;    /* rel is nailed in cache */
    bool        rd_isvalid;     /* relcache entry is valid */
    char        rd_indexvalid;  /* state of rd_indexlist: 0 = not valid, 1 =
                                 * valid, 2 = temporarily forced */
    bool        rd_statvalid;   /* is rd_statlist valid? */

    /*
     * rd_createSubid is the ID of the highest subtransaction the rel has
     * survived into; or zero if the rel was not created in the current top
     * transaction.  This can be now be relied on, whereas previously it could
     * be "forgotten" in earlier releases. Likewise, rd_newRelfilenodeSubid is
     * the ID of the highest subtransaction the relfilenode change has
     * survived into, or zero if not changed in the current transaction (or we
     * have forgotten changing it). rd_newRelfilenodeSubid can be forgotten
     * when a relation has multiple new relfilenodes within a single
     * transaction, with one of them occurring in a subsequently aborted
     * subtransaction, e.g. BEGIN; TRUNCATE t; SAVEPOINT save; TRUNCATE t;
     * ROLLBACK TO save; -- rd_newRelfilenode is now forgotten
     */
    SubTransactionId rd_createSubid;    /* rel was created in current xact */
    SubTransactionId rd_newRelfilenodeSubid;    /* new relfilenode assigned in
                                                 * current xact */

    Form_pg_class rd_rel;       /* RELATION tuple */
    TupleDesc   rd_att;         /* tuple descriptor */
    Oid         rd_id;          /* relation's object id */
    LockInfoData rd_lockInfo;   /* lock mgr's info for locking relation */
    RuleLock   *rd_rules;       /* rewrite rules */
    MemoryContext rd_rulescxt;  /* private memory cxt for rd_rules, if any */
    TriggerDesc *trigdesc;      /* Trigger info, or NULL if rel has none */
    /* use "struct" here to avoid needing to include rowsecurity.h: */
    struct RowSecurityDesc *rd_rsdesc;  /* row security policies, or NULL */

    /* data managed by RelationGetFKeyList: */
    List       *rd_fkeylist;    /* list of ForeignKeyCacheInfo (see below) */
    bool        rd_fkeyvalid;   /* true if list has been computed */

    MemoryContext rd_partkeycxt;    /* private memory cxt for the below */
    struct PartitionKeyData *rd_partkey;    /* partition key, or NULL */
    MemoryContext rd_pdcxt;     /* private context for partdesc */
    struct PartitionDescData *rd_partdesc;  /* partitions, or NULL */
    List       *rd_partcheck;   /* partition CHECK quals */

    /* data managed by RelationGetIndexList: */
    List       *rd_indexlist;   /* list of OIDs of indexes on relation */
    Oid         rd_oidindex;    /* OID of unique index on OID, if any */
    Oid         rd_pkindex;     /* OID of primary key, if any */
    Oid         rd_replidindex; /* OID of replica identity index, if any */

    /* data managed by RelationGetStatExtList: */
    List       *rd_statlist;    /* list of OIDs of extended stats */

    /* data managed by RelationGetIndexAttrBitmap: */
    Bitmapset  *rd_indexattr;   /* columns used in non-projection indexes */
    Bitmapset  *rd_projindexattr;   /* columns used in projection indexes */
    Bitmapset  *rd_keyattr;     /* cols that can be ref'd by foreign keys */
    Bitmapset  *rd_pkattr;      /* cols included in primary key */
    Bitmapset  *rd_idattr;      /* included in replica identity index */
    Bitmapset  *rd_projidx;     /* Oids of projection indexes */

    PublicationActions *rd_pubactions;  /* publication actions */

    /*
     * rd_options is set whenever rd_rel is loaded into the relcache entry.
     * Note that you can NOT look into rd_rel for this data.  NULL means "use
     * defaults".
     */
    bytea      *rd_options;     /* parsed pg_class.reloptions */

    /* These are non-NULL only for an index relation: */
    Form_pg_index rd_index;     /* pg_index tuple describing this index */
    /* use "struct" here to avoid needing to include htup.h: */
    struct HeapTupleData *rd_indextuple;    /* all of pg_index tuple */

    /*
     * index access support info (used only for an index relation)
     *
     * Note: only default support procs for each opclass are cached, namely
     * those with lefttype and righttype equal to the opclass's opcintype. The
     * arrays are indexed by support function number, which is a sufficient
     * identifier given that restriction.
     *
     * Note: rd_amcache is available for index AMs to cache private data about
     * an index.  This must be just a cache since it may get reset at any time
     * (in particular, it will get reset by a relcache inval message for the
     * index).  If used, it must point to a single memory chunk palloc'd in
     * rd_indexcxt.  A relcache reset will include freeing that chunk and
     * setting rd_amcache = NULL.
     */
    Oid         rd_amhandler;   /* OID of index AM's handler function */
    MemoryContext rd_indexcxt;  /* private memory cxt for this stuff */
    /* use "struct" here to avoid needing to include amapi.h: */
    struct IndexAmRoutine *rd_amroutine;    /* index AM's API struct */
    Oid        *rd_opfamily;    /* OIDs of op families for each index col */
    Oid        *rd_opcintype;   /* OIDs of opclass declared input data types */
    RegProcedure *rd_support;   /* OIDs of support procedures */
    FmgrInfo   *rd_supportinfo; /* lookup info for support procedures */
    int16      *rd_indoption;   /* per-column AM-specific flags */
    List       *rd_indexprs;    /* index expression trees, if any */
    List       *rd_indpred;     /* index predicate tree, if any */
    Oid        *rd_exclops;     /* OIDs of exclusion operators, if any */
    Oid        *rd_exclprocs;   /* OIDs of exclusion ops' procs, if any */
    uint16     *rd_exclstrats;  /* exclusion ops' strategy numbers, if any */
    void       *rd_amcache;     /* available for use by index AM */
    Oid        *rd_indcollation;    /* OIDs of index collations */

    /*
     * foreign-table support
     *
     * rd_fdwroutine must point to a single memory chunk palloc'd in
     * CacheMemoryContext.  It will be freed and reset to NULL on a relcache
     * reset.
     */

    /* use "struct" here to avoid needing to include fdwapi.h: */
    struct FdwRoutine *rd_fdwroutine;   /* cached function pointers, or NULL */

    /*
     * Hack for CLUSTER, rewriting ALTER TABLE, etc: when writing a new
     * version of a table, we need to make any toast pointers inserted into it
     * have the existing toast table's OID, not the OID of the transient toast
     * table.  If rd_toastoid isn't InvalidOid, it is the OID to place in
     * toast pointers inserted into this rel.  (Note it's set on the new
     * version of the main heap, not the toast table itself.)  This also
     * causes toast_save_datum() to try to preserve toast value OIDs.
     */
    Oid         rd_toastoid;    /* Real TOAST table's OID, or InvalidOid */

    /* use "struct" here to avoid needing to include pgstat.h: */
    struct PgStat_TableStatus *pgstat_info; /* statistics collection area */
} RelationData;

typedef struct RelationData *Relation;

/* ----------------
 *      RelationPtr is used in the executor to support index scans
 *      where we have to keep track of several index relations in an
 *      array.  -cim 9/10/89
 * ----------------
 */
typedef Relation *RelationPtr;

三、函数调用信息

  1. 执行的SQL
postgres=# select s.*
from t_student s
where s.sdept='IS'
order by s.sno desc
limit 2;
  1. 函数调用栈信息
Breakpoint 3, addRangeTableEntry (pstate=0x1f98638, relation=0x1f97e48,
    alias=0x1f97ea0, inh=true, inFromCl=true) at parse_relation.c:1200
1200            RangeTblEntry *rte = makeNode(RangeTblEntry);
(gdb) bt
/*  函数调用栈信息 */
#0  addRangeTableEntry (pstate=0x1f98638, relation=0x1f97e48, alias=0x1f97ea0,
    inh=true, inFromCl=true) at parse_relation.c:1200
#1  0x00000000005ec7ed in transformTableEntry (pstate=0x1f98638, r=0x1f97e48)
    at parse_clause.c:435
#2  0x00000000005ee210 in transformFromClauseItem (pstate=0x1f98638,
    n=0x1f97e48, top_rte=0x7ffc63ea0a28, top_rti=0x7ffc63ea0a24,
    namespace=0x7ffc63ea0a18) at parse_clause.c:1121
#3  0x00000000005ec0d3 in transformFromClause (pstate=0x1f98638,
    frmList=0x1f97f00) at parse_clause.c:139
#4  0x00000000005b54a3 in transformSelectStmt (pstate=0x1f98638,
    stmt=0x1f981c0) at analyze.c:1212
#5  0x00000000005b3a43 in transformStmt (pstate=0x1f98638, parseTree=0x1f981c0)
    at analyze.c:301
#6  0x00000000005b3919 in transformOptionalSelectInto (pstate=0x1f98638,
    parseTree=0x1f981c0) at analyze.c:246
#7  0x00000000005b37d2 in transformTopLevelStmt (pstate=0x1f98638,
    parseTree=0x1f985a0) at analyze.c:196
#8  0x00000000005b361b in parse_analyze (parseTree=0x1f985a0,
    sourceText=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;", paramTypes=0x0, numParams=0, queryEnv=0x0)
    at analyze.c:116
#9  0x00000000008c31dd in pg_analyze_and_rewrite (parsetree=0x1f985a0,
    query_string=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\nor---Type <return> to continue, or q <return> to quit---
der by s.sno desc \nlimit 2;", paramTypes=0x0, numParams=0, queryEnv=0x0)
    at postgres.c:666
#10 0x00000000008c3847 in exec_simple_query (
    query_string=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;") at postgres.c:1047
#11 0x00000000008c7d23 in PostgresMain (argc=1, argv=0x1fc31a0,
    dbname=0x1fc3000 "postgres", username=0x1fc2fe0 "appusr")
    at postgres.c:4153
#12 0x000000000082405c in BackendRun (port=0x1fb8fb0) at postmaster.c:4361
#13 0x00000000008237c0 in BackendStartup (port=0x1fb8fb0) at postmaster.c:4033
#14 0x000000000081fb58 in ServerLoop () at postmaster.c:1706
#15 0x000000000081f3f0 in PostmasterMain (argc=3, argv=0x1f91ce0)
    at postmaster.c:1379
#16 0x00000000007469d4 in main (argc=3, argv=0x1f91ce0) at main.c:228
/*  函数调用参数 */
(gdb) p *pstate
$122 = {parentParseState = 0x0,
  p_sourcetext = 0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;", p_rtable = 0x0, p_joinexprs = 0x0,
  p_joinlist = 0x0, p_namespace = 0x0, p_lateral_active = false,
  p_ctenamespace = 0x0, p_future_ctes = 0x0, p_parent_cte = 0x0,
  p_target_relation = 0x0, p_target_rangetblentry = 0x0, p_is_insert = false,
  p_windowdefs = 0x0, p_expr_kind = EXPR_KIND_NONE, p_next_resno = 1,
  p_multiassign_exprs = 0x0, p_locking_clause = 0x0,
  p_locked_from_parent = false, p_resolve_unknowns = true, p_queryEnv = 0x0,
  p_hasAggs = false, p_hasWindowFuncs = false, p_hasTargetSRFs = false,
  p_hasSubLinks = false, p_hasModifyingCTE = false, p_last_srf = 0x0,
  p_pre_columnref_hook = 0x0, p_post_columnref_hook = 0x0,
  p_paramref_hook = 0x0, p_coerce_param_hook = 0x0, p_ref_hook_state = 0x0}
(gdb) p *relation
$123 = {type = T_RangeVar, catalogname = 0x0, schemaname = 0x0,
  relname = 0x1f97e00 "t_student", inh = true, relpersistence = 112 'p',
  alias = 0x1f97ea0, location = 16}
(gdb) p *relation->alias
$124 = {type = T_Alias, aliasname = 0x1f97e28 "s", colnames = 0x0}
(gdb) p *alias
$125 = {type = T_Alias, aliasname = 0x1f97e28 "s", colnames = 0x0}
/*  函数运行过程 */
(gdb) n
1201            char       *refname = alias ? alias->aliasname : relation->relname;
(gdb) p *rte
$126 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relid = 0,
  relkind = 0 '\000', tablesample = 0x0, subquery = 0x0,
  security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0,
  functions = 0x0, funcordinality = false, tablefunc = 0x0,
  values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, self_reference = false,
  coltypes = 0x0, coltypmods = 0x0, colcollations = 0x0, enrname = 0x0,
  enrtuples = 0, alias = 0x0, eref = 0x0, lateral = false, inh = false,
  inFromCl = false, requiredPerms = 0, checkAsUser = 0, selectedCols = 0x0,
  insertedCols = 0x0, updatedCols = 0x0, securityQuals = 0x0}
(gdb) n
1205            Assert(pstate != NULL);
(gdb) p *refname
$127 = 115 's'
(gdb) p refname
$128 = 0x1f97e28 "s"
(gdb) n
1207            rte->rtekind = RTE_RELATION;
(gdb) n
1208            rte->alias = alias;
(gdb) n
1216            lockmode = isLockedRefname(pstate, refname) ? RowShareLock : AccessShareLock;// 根据查询分析,判断锁表类型
(gdb) n
1217            rel = parserOpenTable(pstate, relation, lockmode); // 从缓存中获取关系缓存实体
(gdb) p lockmode
$129 = 1
(gdb) n
1218            rte->relid = RelationGetRelid(rel); // 获取关系缓存实体ID
(gdb) p *rel
$130 = {rd_node = {spcNode = 1663, dbNode = 13878, relNode = 16408},
  rd_smgr = 0x2021f40, rd_refcnt = 1, rd_backend = -1, rd_islocaltemp = false,
  rd_isnailed = false, rd_isvalid = true, rd_indexvalid = 1 '\001',
  rd_statvalid = true, rd_createSubid = 0, rd_newRelfilenodeSubid = 0,
  rd_rel = 0x7fcfc6d309a0, rd_att = 0x7fcfc6d30ab8, rd_id = 16408,
  rd_lockInfo = {lockRelId = {relId = 16408, dbId = 13878}}, rd_rules = 0x0,
  rd_rulescxt = 0x0, trigdesc = 0x0, rd_rsdesc = 0x0, rd_fkeylist = 0x0,
  rd_fkeyvalid = false, rd_partkeycxt = 0x0, rd_partkey = 0x0, rd_pdcxt = 0x0,
  rd_partdesc = 0x0, rd_partcheck = 0x0, rd_indexlist = 0x7fcfc6c72668,
  rd_oidindex = 0, rd_pkindex = 16411, rd_replidindex = 16411,
  rd_statlist = 0x0, rd_indexattr = 0x0, rd_projindexattr = 0x0,
  rd_keyattr = 0x0, rd_pkattr = 0x0, rd_idattr = 0x0, rd_projidx = 0x0,
  rd_pubactions = 0x0, rd_options = 0x0, rd_index = 0x0, rd_indextuple = 0x0,
  rd_amhandler = 0, rd_indexcxt = 0x0, rd_amroutine = 0x0, rd_opfamily = 0x0,
  rd_opcintype = 0x0, rd_support = 0x0, rd_supportinfo = 0x0,
  rd_indoption = 0x0, rd_indexprs = 0x0, rd_indpred = 0x0, rd_exclops = 0x0,
  rd_exclprocs = 0x0, rd_exclstrats = 0x0, rd_amcache = 0x0,
  rd_indcollation = 0x0, rd_fdwroutine = 0x0, rd_toastoid = 0,
  pgstat_info = 0x2015c60}
(gdb) n
1219            rte->relkind = rel->rd_rel->relkind; // 关系实体类型
(gdb) p rte->relid
$131 = 16408
(gdb) n
1225            rte->eref = makeAlias(refname, NIL);
(gdb) p rte->relkind
$132 = 114 'r'
(gdb) n
1226            buildRelationAliases(rel->rd_att, alias, rte->eref); // 构建别名信息
(gdb)
1233            heap_close(rel, NoLock);
(gdb)
1241            rte->lateral = false;
(gdb)
1242            rte->inh = inh;
(gdb)
1243            rte->inFromCl = inFromCl;
(gdb)
1245            rte->requiredPerms = ACL_SELECT;
(gdb)
1246            rte->checkAsUser = InvalidOid;  /* not set-uid by default, either */
(gdb)
1247            rte->selectedCols = NULL;
(gdb)
1248            rte->insertedCols = NULL;
(gdb)
1249            rte->updatedCols = NULL;
(gdb)
1255            pstate->p_rtable = lappend(pstate->p_rtable, rte); // 将表实例rte附加到pstate->p_rtable列表中
(gdb)
1257            return rte;
(gdb)
1258    }
// 返回RangeTblEntry 对象指针
(gdb) p *rte
$133 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relid = 16408,
  relkind = 114 'r', tablesample = 0x0, subquery = 0x0,
  security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0,
  functions = 0x0, funcordinality = false, tablefunc = 0x0,
  values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, self_reference = false,
  coltypes = 0x0, coltypmods = 0x0, colcollations = 0x0, enrname = 0x0,
  enrtuples = 0, alias = 0x1f97ea0, eref = 0x1f98980, lateral = false,
  inh = true, inFromCl = true, requiredPerms = 2, checkAsUser = 0,
  selectedCols = 0x0, insertedCols = 0x0, updatedCols = 0x0,
  securityQuals = 0x0}
(gdb) p *rte->alias
$134 = {type = T_Alias, aliasname = 0x1f97e28 "s", colnames = 0x0}
(gdb) p *rte->eref
$135 = {type = T_Alias, aliasname = 0x1f989b8 "s", colnames = 0x1f98a48}
(gdb) p *rte->eref->colnames
$136 = {type = T_List, length = 5, head = 0x1f98a20, tail = 0x1f98c18}
(gdb) p *((Node*)rte->eref->colnames->head->data->ptr_value)
$137 = {type = T_String}
(gdb) p *((Value*)rte->eref->colnames->head->data->ptr_value)
$138 = {type = T_String, val = {ival = 33130968, str = 0x1f989d8 "sno"}}
(gdb) p *((Value*)rte->eref->colnames->head->next->data->ptr_value)
$139 = {type = T_String, val = {ival = 33131136, str = 0x1f98a80 "sname"}}
(gdb) p *((Value*)rte->eref->colnames->head->next->next->data->ptr_value)
$140 = {type = T_String, val = {ival = 33131248, str = 0x1f98af0 "ssex"}}
(gdb) p *((Value*)rte->eref->colnames->head->next->next->next->data->ptr_value)
$141 = {type = T_String, val = {ival = 33131360, str = 0x1f98b60 "sage"}}
(gdb) p *((Value*)rte->eref->colnames->head->next->next->next->next->data->ptr_value)
$142 = {type = T_String, val = {ival = 33131472, str = 0x1f98bd0 "sdept"}}
(gdb) n
// 回到上一级函数transformTableEntry 
transformTableEntry (pstate=0x1f98638, r=0x1f97e48) at parse_clause.c:437
437             return rte;
(gdb) n
438     }
(gdb) n
// 回到上一级函数 transformFromClauseItem
transformFromClauseItem (pstate=0x1f98638, n=0x1f97e48,
    top_rte=0x7ffc63ea0a28, top_rti=0x7ffc63ea0a24, namespace=0x7ffc63ea0a18)
    at parse_clause.c:1124
1124                    rtindex = list_length(pstate->p_rtable);
(gdb) bt
#0  transformFromClauseItem (pstate=0x1f98638, n=0x1f97e48,
    top_rte=0x7ffc63ea0a28, top_rti=0x7ffc63ea0a24, namespace=0x7ffc63ea0a18)
    at parse_clause.c:1124
#1  0x00000000005ec0d3 in transformFromClause (pstate=0x1f98638,
    frmList=0x1f97f00) at parse_clause.c:139
#2  0x00000000005b54a3 in transformSelectStmt (pstate=0x1f98638,
    stmt=0x1f981c0) at analyze.c:1212
#3  0x00000000005b3a43 in transformStmt (pstate=0x1f98638, parseTree=0x1f981c0)
    at analyze.c:301
#4  0x00000000005b3919 in transformOptionalSelectInto (pstate=0x1f98638,
    parseTree=0x1f981c0) at analyze.c:246
#5  0x00000000005b37d2 in transformTopLevelStmt (pstate=0x1f98638,
    parseTree=0x1f985a0) at analyze.c:196
#6  0x00000000005b361b in parse_analyze (parseTree=0x1f985a0,
    sourceText=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;", paramTypes=0x0, numParams=0, queryEnv=0x0)
    at analyze.c:116
#7  0x00000000008c31dd in pg_analyze_and_rewrite (parsetree=0x1f985a0,
    query_string=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;", paramTypes=0x0, numParams=0, queryEnv=0x0)
    at postgres.c:666
#8  0x00000000008c3847 in exec_simple_query (
    query_string=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\nor---Type <return> to continue, or q <return> to quit---
der by s.sno desc \nlimit 2;") at postgres.c:1047
#9  0x00000000008c7d23 in PostgresMain (argc=1, argv=0x1fc31a0,
    dbname=0x1fc3000 "postgres", username=0x1fc2fe0 "appusr")
    at postgres.c:4153
#10 0x000000000082405c in BackendRun (port=0x1fb8fb0) at postmaster.c:4361
#11 0x00000000008237c0 in BackendStartup (port=0x1fb8fb0) at postmaster.c:4033
#12 0x000000000081fb58 in ServerLoop () at postmaster.c:1706
#13 0x000000000081f3f0 in PostmasterMain (argc=3, argv=0x1f91ce0)
    at postmaster.c:1379
#14 0x00000000007469d4 in main (argc=3, argv=0x1f91ce0) at main.c:228
(gdb) n
1125                    Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
(gdb)
1126                    *top_rte = rte;
(gdb)
1127                    *top_rti = rtindex;
(gdb)
1128                    *namespace = list_make1(makeDefaultNSItem(rte));
(gdb)
1129                    rtr = makeNode(RangeTblRef);
(gdb) p *namespace
$143 = (List *) 0x1f98cf0
(gdb) p **namespace
$144 = {type = T_List, length = 1, head = 0x1f98cc8, tail = 0x1f98cc8}
(gdb) p *(*namespace)->head->data->ptr_value
Attempt to dereference a generic pointer.
(gdb) p *((Node*)(*namespace)->head->data->ptr_value)
$145 = {type = 33130600}
(gdb) n
1130                    rtr->rtindex = rtindex;
(gdb) n
1131                    return (Node *) rtr;
(gdb) p *rtr
$146 = {type = T_RangeTblRef, rtindex = 1}
(gdb) finish // 回到上一级函数 transformFromClause 
Run till exit from #0  transformFromClauseItem (pstate=0x1f98638, n=0x1f97e48,
    top_rte=0x7ffc63ea0a28, top_rti=0x7ffc63ea0a24, namespace=0x7ffc63ea0a18)
    at parse_clause.c:1131
0x00000000005ec0d3 in transformFromClause (pstate=0x1f98638, frmList=0x1f97f00)
    at parse_clause.c:139
139                     n = transformFromClauseItem(pstate, n,
Value returned is $147 = (Node *) 0x1f98d28
(gdb) bt
#0  0x00000000005ec0d3 in transformFromClause (pstate=0x1f98638,
    frmList=0x1f97f00) at parse_clause.c:139
#1  0x00000000005b54a3 in transformSelectStmt (pstate=0x1f98638,
    stmt=0x1f981c0) at analyze.c:1212
#2  0x00000000005b3a43 in transformStmt (pstate=0x1f98638, parseTree=0x1f981c0)
    at analyze.c:301
#3  0x00000000005b3919 in transformOptionalSelectInto (pstate=0x1f98638,
    parseTree=0x1f981c0) at analyze.c:246
#4  0x00000000005b37d2 in transformTopLevelStmt (pstate=0x1f98638,
    parseTree=0x1f985a0) at analyze.c:196
#5  0x00000000005b361b in parse_analyze (parseTree=0x1f985a0,
    sourceText=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;", paramTypes=0x0, numParams=0, queryEnv=0x0)
    at analyze.c:116
#6  0x00000000008c31dd in pg_analyze_and_rewrite (parsetree=0x1f985a0,
    query_string=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;", paramTypes=0x0, numParams=0, queryEnv=0x0)
    at postgres.c:666
#7  0x00000000008c3847 in exec_simple_query (
    query_string=0x1f971c8 "select s.*\nfrom t_student s\nwhere s.sdept='IS'\norder by s.sno desc \nlimit 2;") at postgres.c:1047
#8  0x00000000008c7d23 in PostgresMain (argc=1, argv=0x1fc31a0,
    dbname=0x1fc3000 "postgres", username=0x1fc2fe0 "appusr")
---Type <return> to continue, or q <return> to quit---
    at postgres.c:4153
#9  0x000000000082405c in BackendRun (port=0x1fb8fb0) at postmaster.c:4361
#10 0x00000000008237c0 in BackendStartup (port=0x1fb8fb0) at postmaster.c:4033
#11 0x000000000081fb58 in ServerLoop () at postmaster.c:1706
#12 0x000000000081f3f0 in PostmasterMain (argc=3, argv=0x1f91ce0)
    at postmaster.c:1379
#13 0x00000000007469d4 in main (argc=3, argv=0x1f91ce0) at main.c:228
(gdb) n
144                     checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
(gdb) n
147                     setNamespaceLateralState(namespace, true, true);
(gdb) n
149                     pstate->p_joinlist = lappend(pstate->p_joinlist, n);
(gdb) n
150                     pstate->p_namespace = list_concat(pstate->p_namespace, namespace);
(gdb) n
132             foreach(fl, frmList)
(gdb) n
159             setNamespaceLateralState(pstate->p_namespace, false, true);
(gdb) n
160     }
(gdb) 

四、总结

1、通过观察addRangeTableEntry的执行过程,了解SQL语义解析transformFromClause的处理过程。
2、表结构信是从缓存中结构读取,然后获取自己需要的信息。
3、语义分析后转换为relid(关联对象id),提升查询执行的处理效率。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容