sharding-sphere之SQL解析insert

以mysql為例,官網(wǎng)說明insert語(yǔ)句的寫法如下:

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [PARTITION (partition_name [, partition_name] ...)]
    [(col_name [, col_name] ...)]
    {VALUES | VALUE} (value_list) [, (value_list)] ...
    [ON DUPLICATE KEY UPDATE assignment_list]

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [PARTITION (partition_name [, partition_name] ...)]
    SET assignment_list
    [ON DUPLICATE KEY UPDATE assignment_list]

INSERT [LOW_PRIORITY | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [PARTITION (partition_name [, partition_name] ...)]
    [(col_name [, col_name] ...)]
    SELECT ...
    [ON DUPLICATE KEY UPDATE assignment_list]

value:
    {expr | DEFAULT}

value_list:
    value [, value] ...

assignment:
    col_name = value

assignment_list:
    assignment [, assignment] ...

簡(jiǎn)單來講渔呵,支持一下三種:

--第一種Insert into values
insert into table(column1,column2,column3...) values (data1,data2,data3...)
--第二種Insert into set
insert into table set column1=data1,column2=data2,column3=data3...
--第三種
insert into table select ...

目前來講,sharding-sphere并不支持第三種忧额。原因在這里:

 //如果詞法解析器解析表名后面為select關(guān)鍵字厘肮,則拋異常愧口。
 if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {
     throw new UnsupportedOperationException("Cannot INSERT SELECT");
 }

接下來睦番,就以第一種為例,介紹一下整個(gè)的語(yǔ)法解析過程:
解析sql:

INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)

語(yǔ)法解析器會(huì)將sql最終解析成SqlStatement耍属,sql的解析都會(huì)被Sql語(yǔ)法解析引擎SQLParsingEngine去解析托嚣,從SQL解析引擎看起,看這條insert sql都經(jīng)歷了些什么故事厚骗?

@Test
public void insertValuesTest() {
    ShardingRule shardingRule = createShardingRuleByLocalHost();
    String insertSQL="INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)";
    //初始化一個(gè)語(yǔ)法解析器
    SQLParsingEngine statementParser = new SQLParsingEngine(DatabaseType.MySQL,insertSQL, shardingRule, null);
    //語(yǔ)法解析器解析
    InsertStatement insertStatement = (InsertStatement) statementParser.parse(false);
    System.out.println(insertStatement);
}

SQLParsingEngine解析過程中示启,首先判斷是否需要讀緩存,如果需要?jiǎng)t從緩存中取领舰,如果不需要夫嗓,則初始化一個(gè)詞法解析器LexerEngine,獲取第一個(gè)分詞,根據(jù)分詞冲秽,數(shù)據(jù)庫(kù)類型獲取真實(shí)的SqlParser舍咖,這里為MySQLInsertParser,具體過程在鏈接:
詞法解析器

public SQLStatement parse(final boolean useCache) {
    //是否讀緩存
    Optional<SQLStatement> cachedSQLStatement = getSQLStatementFromCache(useCache);
    if (cachedSQLStatement.isPresent()) {
        return cachedSQLStatement.get();
    }
    //詞法解析引擎初始化
    LexerEngine lexerEngine = LexerEngineFactory.newInstance(dbType, sql);
    //分詞锉桑,獲取第一個(gè)分詞排霉,此處為insert
    lexerEngine.nextToken(); //此處為insert分詞
    //獲取語(yǔ)法解析器,并解析
    SQLStatement result = SQLParserFactory.newInstance(dbType, lexerEngine.getCurrentToken().getType(), shardingRule, lexerEngine, shardingMetaData).parse();
    if (useCache) {
        ParsingResultCache.getInstance().put(sql, result);
    }
    return result;
}

MySQLInsertParser解析過程中民轴,首先獲取insert之后的下一個(gè)分詞攻柠,然后交由下一個(gè)從句解析器去解析,比如:insert之后后裸,會(huì)解析into瑰钮, 表名,交由InsertIntoClauseParser去解析微驶,然后解析(,)里的字段信息浪谴,交由InsertColumnsClauseParser去解析,然后判斷是否是insert...select語(yǔ)句,如果是较店,則拋異常士八,表示不支持,否則梁呈,當(dāng)做insert..values語(yǔ)句處理婚度,交由InsertValuesClauseParser處理,若不是insert..values語(yǔ)句官卡,則交由InsertSetClauseParser蝗茁,當(dāng)做insert...set語(yǔ)句處理。

@Override
      public final DMLStatement parse() {
        //獲取下一分詞
        lexerEngine.nextToken();
        //初始化返回的結(jié)果
        InsertStatement result = new InsertStatement();
        //InsertIntoClauseParser insertInto從句解析器解析結(jié)果
        insertClauseParserFacade.getInsertIntoClauseParser().parse(result);
        //解析()及里面的字段信息
        insertClauseParserFacade.getInsertColumnsClauseParser().parse(result, shardingMetaData);
        //如果是insert select 語(yǔ)句寻咒,則不支持
        if (lexerEngine.equalAny(DefaultKeyword.SELECT, Symbol.LEFT_PAREN)) {
            throw new UnsupportedOperationException("Cannot INSERT SELECT");
        }
        //InsertValuesClauseParser解析insert value信息
        insertClauseParserFacade.getInsertValuesClauseParser().parse(result, shardingMetaData);
        //InsertSetClauseParser 解析insert...set信息
        insertClauseParserFacade.getInsertSetClauseParser().parse(result);
        //ON DUPLICATE KEY UPDATE 解析
        insertClauseParserFacade.getInsertDuplicateKeyUpdateClauseParser().parse(result);
        //處理需要自動(dòng)生成值的列
        processGeneratedKey(result);
        return result;
    }

InsertIntoClauseParser的處理流程:

public void parse(final InsertStatement insertStatement) {
    //如果出現(xiàn)不支持的詞在into之前哮翘,則終止解析    
    lexerEngine.unsupportedIfEqual(getUnsupportedKeywordsBeforeInto());
    //跳過所有字符,直到出現(xiàn)into
    lexerEngine.skipUntil(DefaultKeyword.INTO);
    //獲取into后的下一分詞毛秘,當(dāng)然這里就是表名了
    lexerEngine.nextToken();
    //解析表引用關(guān)系
    tableReferencesClauseParser.parse(insertStatement, true);
    skipBetweenTableAndValues(insertStatement);
}

而在解析表引用關(guān)系時(shí)饭寺,實(shí)質(zhì)是使用MySQLTableReferencesClauseParser,mysql的table從句解析器解析,具體如下:

@Override
protected void parseTableReference(final SQLStatement sqlStatement, final boolean isSingleTableOnly) {
    //解析表
    parseTableFactor(sqlStatement, isSingleTableOnly);
    //PARTITION 解析
    parsePartition();
    parseIndexHint(sqlStatement);
}  

protected final void parseTableFactor(final SQLStatement sqlStatement, final boolean isSingleTableOnly) {
    final int beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    String literals = lexerEngine.getCurrentToken().getLiterals();
    int skippedSchemaNameLength = 0;
    //獲取下一分詞
    lexerEngine.nextToken();
    //跳過如果是. 則在點(diǎn)之前的是schema的名稱叫挟,跳過.
    if (lexerEngine.skipIfEqual(Symbol.DOT)) {
         //跳過schema的長(zhǎng)度為skippedSchemaNameLength
        skippedSchemaNameLength = literals.length() + Symbol.DOT.getLiterals().length();
        //表名為當(dāng)前分詞
        literals = lexerEngine.getCurrentToken().getLiterals();
    }
    //獲取表名 如果為`tablename`,則返回tablename
    String tableName = SQLUtil.getExactlyValue(literals);
    if (Strings.isNullOrEmpty(tableName)) {
        return;
    }
    //解析別名 
    Optional<String> alias = aliasExpressionParser.parseTableAlias();
    //如果是單表艰匙,或者能根據(jù)邏輯實(shí)體表名獲取到表規(guī)則等其他條件
    if (isSingleTableOnly || shardingRule.tryFindTableRuleByLogicTable(tableName).isPresent() || shardingRule.findBindingTableRule(tableName).isPresent()
            || shardingRule.getShardingDataSourceNames().getDataSourceNames().contains(shardingRule.getShardingDataSourceNames().getDefaultDataSourceName())) {
        //表的信息記錄在返回值里,有數(shù)據(jù)庫(kù)的schema名稱抹恳,表名稱员凝,開始位置坐標(biāo)
        sqlStatement.getSqlTokens().add(new TableToken(beginPosition, skippedSchemaNameLength, literals));
        //表名和別名記錄下來
        sqlStatement.getTables().add(new Table(tableName, alias));
    }
    //解析是否強(qiáng)制索引,insert語(yǔ)句直接跳過奋献,不會(huì)使用
    parseForceIndex(tableName, sqlStatement);
    //表關(guān)聯(lián)解析健霹,直接跳過
    parseJoinTable(sqlStatement);
    //如果參數(shù)是單表,且解析結(jié)果不是單表瓶蚂,則直接拋異常
    if (isSingleTableOnly && !sqlStatement.getTables().isSingleTable()) {
        throw new UnsupportedOperationException("Cannot support Multiple-Table.");
    }
}

insert into table具體如何解析結(jié)束看完了糖埋,接下來,到解析表字段了,InsertColumnsClauseParser出場(chǎng)了扬跋。在InsertColumnsClauseParser處理邏輯中阶捆,從(開始,解析一個(gè)又一個(gè)的分詞钦听,直到碰到)或者結(jié)束分詞洒试。如果沒有(,則獲取全部字段朴上。

public void parse(final InsertStatement insertStatement, final ShardingMetaData shardingMetaData) {
        Collection<Column> result = new LinkedList<>();
        //獲取表名
        String tableName = insertStatement.getTables().getSingleTableName();
        //獲取需要自動(dòng)生成字段的值
        Optional<Column> generateKeyColumn = shardingRule.getGenerateKeyColumn(tableName);
        int count = 0;
        //如果是`(`
        if (lexerEngine.equalAny(Symbol.LEFT_PAREN)) {
            do {
                //獲取下一個(gè)分詞垒棋,就是獲取insert into table(field1,filed2...)中`(`后面的fileld1,或者是`,`后面的field2.
                lexerEngine.nextToken();
                //具體解析里面的字段痪宰,返回值name為具體屬性
                SQLExpression sqlExpression = basicExpressionParser.parse(insertStatement);
                //根據(jù)不同類型叼架,獲取列屬性名稱
                String columnName = null;
                if (sqlExpression instanceof SQLPropertyExpression) {
                    columnName = SQLUtil.getExactlyValue(((SQLPropertyExpression) sqlExpression).getName());
                }
                if (sqlExpression instanceof SQLIdentifierExpression) {
                    columnName = SQLUtil.getExactlyValue(((SQLIdentifierExpression) sqlExpression).getName());
                }
                if (sqlExpression instanceof SQLIgnoreExpression) {
                    columnName = SQLUtil.getExactlyValue(((SQLIgnoreExpression) sqlExpression).getExpression());
                }
                //返回值添加列信息
                result.add(new Column(columnName, tableName));
                if (generateKeyColumn.isPresent() && generateKeyColumn.get().getName().equalsIgnoreCase(columnName)) {
                    //如果有需要自動(dòng)生成列信息的字段畔裕,記錄字段位置
                    insertStatement.setGenerateKeyColumnIndex(count);
                }
                count++;
            } while (!lexerEngine.equalAny(Symbol.RIGHT_PAREN) && !lexerEngine.equalAny(Assist.END));
            insertStatement.setColumnsListLastPosition(lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length());
            lexerEngine.nextToken();
        } else {
          //忽略若干代碼
        }
        insertStatement.getColumns().addAll(result);
}

接下來要解析values后面的值了,慣例是InsertValuesClauseParser乖订,該從句首先判斷是否是values分詞的語(yǔ)句扮饶,如果是,則解析乍构,如果不是則直接跳過甜无,等待后面insert ...set處理。

public void parse(final InsertStatement insertStatement, final ShardingMetaData shardingMetaData) {
    Collection<Keyword> valueKeywords = new LinkedList<>();
    valueKeywords.add(DefaultKeyword.VALUES);
    valueKeywords.addAll(Arrays.asList(getSynonymousKeywordsForValues()));
    //是否是value,或者是values的關(guān)鍵字哥遮,如果是則解析岂丘,如果不是,則跳過
    if (lexerEngine.skipIfEqual(valueKeywords.toArray(new Keyword[valueKeywords.size()]))) {
        //解析value值
        parseValues(insertStatement);
    }
}

private void parseValues(final InsertStatement insertStatement) {
    int beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    int endPosition;
    insertStatement.getSqlTokens().add(new InsertValuesToken(beginPosition, insertStatement.getTables().getSingleTableName()));
    do {
        beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
        //分詞器解析必須是`(`開頭
        lexerEngine.accept(Symbol.LEFT_PAREN);
        List<SQLExpression> sqlExpressions = new LinkedList<>();
        int columnsCount = 0;
        do {
            //解析每一個(gè)分詞sql后面的具體值眠饮,對(duì)應(yīng)sql中的INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)的10和1
            sqlExpressions.add(basicExpressionParser.parse(insertStatement));
            //跳過::
            skipsDoubleColon();
            //列個(gè)數(shù)+1
            columnsCount++;
            //當(dāng)解析器遇到的分詞為,號(hào)時(shí)奥帘,繼續(xù)解析
        } while (lexerEngine.skipIfEqual(Symbol.COMMA));
        //刪除自動(dòng)生成的鍵
        removeGenerateKeyColumn(insertStatement, columnsCount);
        columnsCount = 0;
        int parametersCount = 0;
        AndCondition andCondition = new AndCondition();
        for (Column each : insertStatement.getColumns()) {
            SQLExpression sqlExpression = sqlExpressions.get(columnsCount);
            //如果是分片項(xiàng),則添加Condition
            if (shardingRule.isShardingColumn(each)) {
                andCondition.getConditions().add(new Condition(each, sqlExpression));
            }
            //如果是自動(dòng)生成的列
            if (insertStatement.getGenerateKeyColumnIndex() == columnsCount) {
                //生成Condition
                insertStatement.getGeneratedKeyConditions().add(createGeneratedKeyCondition(each, sqlExpression));
            }
            //列個(gè)數(shù)+1
            columnsCount++;
            if (sqlExpression instanceof SQLPlaceholderExpression) {
                parametersCount++;
            }
        }
        endPosition = lexerEngine.getCurrentToken().getEndPosition();
        //分詞解析器必須以)結(jié)束
        lexerEngine.accept(Symbol.RIGHT_PAREN);
        //組裝數(shù)據(jù)
        insertStatement.getInsertValues().getInsertValues().add(new InsertValue(DefaultKeyword.VALUES, lexerEngine.getInput().substring(beginPosition, endPosition), parametersCount));
        insertStatement.getConditions().getOrCondition().getAndConditions().add(andCondition);
    } while (lexerEngine.skipIfEqual(Symbol.COMMA));
    insertStatement.setInsertValuesListLastPosition(endPosition);
}

如果是insert...set語(yǔ)句,則會(huì)走到InsertSetClauseParser,去解析set語(yǔ)句仪召,如果發(fā)現(xiàn)不是set開頭的語(yǔ)句寨蹋,直接return。

public void parse(final InsertStatement insertStatement) {
    //判斷是否是set返咱,如不是钥庇,直接返回
    if (!lexerEngine.skipIfEqual(getCustomizedInsertKeywords())) {
        return;
    }
    //刪除無用的Token
    removeUnnecessaryToken(insertStatement);
    int beginPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    insertStatement.getSqlTokens().add(new InsertValuesToken(beginPosition, insertStatement.getTables().getSingleTableName()));
    int parametersCount = 0;
    do {
        //解析set之后的數(shù)據(jù)庫(kù)列名稱
        SQLExpression sqlExpression = basicExpressionParser.parse(insertStatement);
        Column column = null;
        if (sqlExpression instanceof SQLPropertyExpression) {
            column = new Column(SQLUtil.getExactlyValue(((SQLPropertyExpression) sqlExpression).getName()), insertStatement.getTables().getSingleTableName());
        }
        if (sqlExpression instanceof SQLIdentifierExpression) {
            column = new Column(SQLUtil.getExactlyValue(((SQLIdentifierExpression) sqlExpression).getName()), insertStatement.getTables().getSingleTableName());
        }
        if (sqlExpression instanceof SQLIgnoreExpression) {
            column = new Column(SQLUtil.getExactlyValue(((SQLIgnoreExpression) sqlExpression).getExpression()), insertStatement.getTables().getSingleTableName());
        }
        //列屬性后面必須緊跟=號(hào)
        lexerEngine.accept(Symbol.EQ);
        //獲取=號(hào)下一分詞類型牍鞠,根據(jù)類型組裝
        if (lexerEngine.equalAny(Literals.INT)) {
            sqlExpression = new SQLNumberExpression(Integer.parseInt(lexerEngine.getCurrentToken().getLiterals()));
        } else if (lexerEngine.equalAny(Literals.FLOAT)) {
            sqlExpression = new SQLNumberExpression(Double.parseDouble(lexerEngine.getCurrentToken().getLiterals()));
        } else if (lexerEngine.equalAny(Literals.CHARS)) {
            sqlExpression = new SQLTextExpression(lexerEngine.getCurrentToken().getLiterals());
        } else if (lexerEngine.equalAny(DefaultKeyword.NULL)) {
            sqlExpression = new SQLIgnoreExpression(DefaultKeyword.NULL.name());
        } else if (lexerEngine.equalAny(Symbol.QUESTION)) {
            sqlExpression = new SQLPlaceholderExpression(insertStatement.getParametersIndex());
            insertStatement.increaseParametersIndex();
            parametersCount++;
        } else {
            throw new UnsupportedOperationException("");
        }
        //獲取下一分詞
        lexerEngine.nextToken();
        if (lexerEngine.equalAny(Symbol.COMMA, DefaultKeyword.ON, Assist.END)) {
            //組裝Condition
            insertStatement.getConditions().add(new Condition(column, sqlExpression), shardingRule);
        } else {
            //跳過咖摹,直到出現(xiàn),on
            lexerEngine.skipUntil(Symbol.COMMA, DefaultKeyword.ON);
        }
    //如果是,結(jié)尾,繼續(xù)循環(huán)难述,解析其他的字段
    } while (lexerEngine.skipIfEqual(Symbol.COMMA));
    //組裝返回?cái)?shù)據(jù)
    int endPosition = lexerEngine.getCurrentToken().getEndPosition() - lexerEngine.getCurrentToken().getLiterals().length();
    insertStatement.getInsertValues().getInsertValues().add(new InsertValue(DefaultKeyword.VALUES, lexerEngine.getInput().substring(beginPosition, endPosition), parametersCount));
    insertStatement.setInsertValuesListLastPosition(endPosition);
}

insert語(yǔ)句解析過程結(jié)束萤晴,解析過程復(fù)雜,能用如此簡(jiǎn)潔的代碼寫完胁后,代碼功底深厚令人折服店读。大寫的服。但其實(shí)還是有一些能優(yōu)化的點(diǎn)攀芯。
最后屯断,再看一下InsertStatement的信息,解析之后侣诺,該對(duì)象組裝了些啥殖演。

InsertStatement.png

對(duì)應(yīng)sql

INSERT INTO `table` (`field1`, `field2`) VALUES (10, 1)
public final class InsertStatement extends DMLStatement {
    //列屬性信息
    private final Collection<Column> columns = new LinkedList<>();
    //自動(dòng)生成的表字段信息
    private List<GeneratedKeyCondition> generatedKeyConditions = new LinkedList<>();
    //插入的值
    private final InsertValues insertValues = new InsertValues();
    //數(shù)據(jù)庫(kù)字段屬性最后結(jié)束的位置
    private int columnsListLastPosition;
    //自動(dòng)生成鍵的位置
    private int generateKeyColumnIndex = -1;
    //insert values最后結(jié)束的位置
    private int insertValuesListLastPosition;
}

public abstract class AbstractSQLStatement implements SQLStatement {
    //sql類型
    private final SQLType type;
    //表信息
    private final Tables tables = new Tables();
    //分片信息
    private final Conditions conditions = new Conditions();
    //sql分詞信息
    private final List<SQLToken> sqlTokens = new LinkedList<>();
    
    private int parametersIndex;

}
InsertStatement.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市年鸳,隨后出現(xiàn)的幾起案子趴久,更是在濱河造成了極大的恐慌,老刑警劉巖搔确,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彼棍,死亡現(xiàn)場(chǎng)離奇詭異灭忠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)座硕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門弛作,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人华匾,你說我怎么就攤上這事缆蝉。” “怎么了瘦真?”我有些...
    開封第一講書人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵刊头,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我诸尽,道長(zhǎng)原杂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任您机,我火速辦了婚禮穿肄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘际看。我一直安慰自己咸产,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開白布仲闽。 她就那樣靜靜地躺著脑溢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赖欣。 梳的紋絲不亂的頭發(fā)上屑彻,一...
    開封第一講書人閱讀 51,231評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音顶吮,去河邊找鬼社牲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛悴了,可吹牛的內(nèi)容都是我干的搏恤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼湃交,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼熟空!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巡揍,我...
    開封第一講書人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤痛阻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后腮敌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阱当,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俏扩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弊添。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片录淡。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖油坝,靈堂內(nèi)的尸體忽然破棺而出嫉戚,到底是詐尸還是另有隱情,我是刑警寧澤澈圈,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布彬檀,位于F島的核電站,受9級(jí)特大地震影響瞬女,放射性物質(zhì)發(fā)生泄漏窍帝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一诽偷、第九天 我趴在偏房一處隱蔽的房頂上張望坤学。 院中可真熱鬧,春花似錦报慕、人聲如沸深浮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)飞苇。三九已至,卻和暖如春洋闽,著一層夾襖步出監(jiān)牢的瞬間玄柠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工诫舅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宫患。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓刊懈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親娃闲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虚汛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx閱讀 8,328評(píng)論 0 16
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法皇帮,內(nèi)部類的語(yǔ)法卷哩,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法属拾,線程的語(yǔ)...
    子非魚_t_閱讀 31,625評(píng)論 18 399
  • artTemplate是騰訊公司的js模板引擎将谊,性能冷溶、語(yǔ)法以及錯(cuò)誤處理非常的優(yōu)秀,它采用預(yù)編譯方式讓性能有了質(zhì)的飛...
    白水螺絲閱讀 1,430評(píng)論 0 4
  • 目 錄 |惟有蘭花香正好 上一章 |火災(zāi) 文 |唐媽 馬家人說是便飯尊浓,便真的是便飯逞频。不過讓我感激不盡的是我們到達(dá)...
    唐媽閱讀 1,691評(píng)論 40 55
  • 1??每個(gè)人都需要白藜蘆醇?? 各個(gè)年齡層的人都有可能產(chǎn)生癌癥,由于DNA的損傷會(huì)隨著年齡而累積...
    王昱霏閱讀 413評(píng)論 0 0