Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import lombok.Getter;
import org.hswebframework.ezorm.rdb.metadata.DataType;
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.metadata.dialect.DataTypeBuilder;

import java.sql.JDBCType;
import java.sql.SQLType;


@Getter
public class JsonType implements DataType {
public class JsonType implements DataType, DataTypeBuilder {

public static JsonType INSTANCE = new JsonType();

Expand All @@ -29,8 +31,12 @@ public String getName() {

@Override
public SQLType getSqlType() {
return JDBCType.CLOB;
return JDBCType.OTHER;
}

@Override
public String createColumnDataType(RDBColumnMetadata columnMetaData) {
return "json";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.hswebframework.ezorm.rdb.metadata.DataType;
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.metadata.dialect.DataTypeBuilder;

import java.sql.JDBCType;
import java.sql.SQLType;

@Getter
@AllArgsConstructor
public class JsonbType implements DataType {
public class JsonbType implements DataType , DataTypeBuilder {
public static JsonbType INSTANCE = new JsonbType();

@Override
Expand All @@ -29,6 +31,11 @@ public String getName() {

@Override
public SQLType getSqlType() {
return JDBCType.CLOB;
return JDBCType.OTHER;
}

@Override
public String createColumnDataType(RDBColumnMetadata columnMetaData) {
return "jsonb";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.hswebframework.ezorm.rdb.supports.postgres;

import com.google.common.collect.Lists;
import lombok.SneakyThrows;
import org.hswebframework.ezorm.core.param.Term;
import org.hswebframework.ezorm.rdb.codec.JsonValueCodec;
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.*;
import org.hswebframework.ezorm.rdb.operator.builder.fragments.term.AbstractTermFragmentBuilder;
import org.hswebframework.ezorm.rdb.utils.SqlUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class PostgresqlJsonbExistTermFragmentBuilder extends AbstractTermFragmentBuilder {

public static final PostgresqlJsonbExistTermFragmentBuilder exist = new PostgresqlJsonbExistTermFragmentBuilder("exist", "jsonb包含");

interface Options {
String contains = "contains";
String contained = "contained";
String all = "all";
String any = "any";
}


public PostgresqlJsonbExistTermFragmentBuilder(String termType, String name) {
super(termType, name);
}


@Override
public SqlFragments createFragments(String columnFullName, RDBColumnMetadata column, Term term) {
String operator = getOperator(term);
if (Operator.needObject(operator)) {
PrepareSqlFragments fragments = PrepareSqlFragments.of();
fragments.addSql(columnFullName, operator);
Object value = term.getValue();
return appendPrepareOrNative(fragments, convertObjectValue(column, value));
}
if (!Operator.base.equals(operator)) {
//转换值
List<Object> values = convertList(term.getValue());
if (values.isEmpty()) {
return EmptySqlFragments.INSTANCE;
}
return new BatchSqlFragments(4, 1)
.addSql(operator, "(", columnFullName, ",")
.addSql("array[")
.add(SqlUtils.createQuestionMarks(values.size()))
.addSql("])")
.addParameter(values);
}

PrepareSqlFragments fragments = PrepareSqlFragments.of();
fragments.addSql(Operator.base, "(", columnFullName, ",");
appendPrepareOrNative(fragments, term.getValue());
return fragments.addSql(")");
}

@SneakyThrows
private Object convertObjectValue(RDBColumnMetadata column, Object value) {
if (value instanceof NativeSql) {
return value;
}
if (column.getValueCodec() != null) {
return column.getValueCodec().encode(value);
}
Object obj;
if (value == null) {
return null;
}
if (value instanceof CharSequence) {
obj = value.toString();
} else {
obj = JsonValueCodec.defaultMapper.writeValueAsString(value);
}
return NativeSql.of("?::jsonb", obj);
}


private List<Object> convertList(Object value) {
if (value == null) {
return Collections.emptyList();
}
if (value instanceof String v) {
value = v.split(",");
}
if (value instanceof Object[] v) {
return Lists.newArrayList(v);
}
if (value instanceof Collection<?> v) {
return new ArrayList<>(v);
}
return Collections.singletonList(value);
}

public static String getOperator(Term term) {
List<String> options = term.getOptions();
if (options.contains(Options.contains)) {
return Operator.contains;
}
if (options.contains(Options.contained)) {
return Operator.contained;
}
if (options.contains(Options.all)) {
return Operator.all;
}
if (options.contains(Options.any)) {
return Operator.any;
}
return Operator.base;
}

private interface Operator {
//用函数规避SimpleParameterList#checkAllParametersSet的检查
String base = "jsonb_exists";
String all = "jsonb_exists_all";
String any = "jsonb_exists_any";
String contains = "@>";
String contained = "<@";

static boolean needObject(String operator) {
return contains.equals(operator) || contained.equals(operator);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ public RDBTableMetadata newTable(String name) {
column.addFeature(new PostgresqlVectorDistanceFunctionFragmentBuilder());
PostgresqlVectorDistanceTermFragmentBuilder.ALL.values().forEach(column::addFeature);
}
if (column.getType() instanceof JsonbType) {
column.addFeature(PostgresqlJsonbExistTermFragmentBuilder.exist);
}
});
return metadata;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ public static StringBuilder replaceSqlParameter(String sql,
* - jsonb_column ? array['key1', 'key2']
* <p>
* 判断逻辑:
* 1. 前面(跳过空格)必须是标识符字符(字母、数字、下划线、右括号、右方括号
* 2. 后面(跳过空格)必须是单引号字符串或 array[
* 1. 前面(跳过空格)必须是标识符字符(字母、数字、下划线、右括号、右方括号或字段名引号
* 2. 后面(跳过空格)必须是单引号字符串、参数占位符?或 array[
*
* @param sql SQL 语句
* @param index '?' 的位置
Expand All @@ -311,9 +311,10 @@ private static boolean isPostgresOperator(String sql, int index) {
}

char prev = sql.charAt(prevIndex);
// 标识符字符:字母、数字、下划线、右括号、右方括号
// 标识符字符:字母、数字、下划线、右括号、右方括号、引号
// 如果不是这些字符,则不是操作符(可能是 =, >, < 等操作符后的参数占位符)
if (!(Character.isLetterOrDigit(prev)
|| prev == '"'
|| prev == '_'
|| prev == ')'
|| prev == ']')) {
Expand All @@ -337,8 +338,8 @@ private static boolean isPostgresOperator(String sql, int index) {

char next = sql.charAt(nextIndex);

// 检查是否是单引号字符串('key')
if (next == '\'') {
// 检查是否是单引号字符串('key')或 参数占位符 (?)
if (next == '\'' || next == '?') {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
package org.hswebframework.ezorm.rdb.supports.postgres;

import org.hswebframework.ezorm.rdb.TestSyncSqlExecutor;
import org.hswebframework.ezorm.rdb.executor.SqlRequests;
import org.hswebframework.ezorm.rdb.executor.SyncSqlExecutor;
import org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrappers;
import org.hswebframework.ezorm.rdb.metadata.RDBColumnMetadata;
import org.hswebframework.ezorm.rdb.metadata.RDBDatabaseMetadata;
import org.hswebframework.ezorm.rdb.metadata.RDBSchemaMetadata;
import org.hswebframework.ezorm.rdb.metadata.RDBTableMetadata;
import org.hswebframework.ezorm.rdb.metadata.dialect.Dialect;
import org.hswebframework.ezorm.rdb.operator.DatabaseOperator;
import org.hswebframework.ezorm.rdb.operator.DefaultDatabaseOperator;
import org.hswebframework.ezorm.rdb.supports.BasicCommonTests;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.sql.JDBCType;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;

public class PostgresqlBasicTest extends BasicCommonTests {
@Override
Expand All @@ -21,4 +38,107 @@ protected Dialect getDialect() {
protected SyncSqlExecutor getSqlExecutor() {
return new TestSyncSqlExecutor(new PostgresqlConnectionProvider());
}

String jsonbTableName = "test_jsonb_exist_sync";

@Test
public void testJsonbExistTerm() {
RDBDatabaseMetadata database = getDatabase();
DatabaseOperator operator = DefaultDatabaseOperator.of(database);

try {

operator.sql()
.sync()
.execute(SqlRequests.of("insert into public." + jsonbTableName + " (id,data) values (?, ?::jsonb)",
"1",
"{\"name\":\"JetLinks\",\"age\":18}"));
operator.sql()
.sync()
.execute(SqlRequests.of("insert into public." + jsonbTableName + " (id,data) values (?, ?::jsonb)",
"2",
"{\"name\":\"Other\"}"));
operator.sql()
.sync()
.execute(SqlRequests.of("insert into public." + jsonbTableName + " (id,data) values (?, ?::jsonb)",
"3",
"{\"age\":20,\"status\":\"ok\"}"));

List<String> existsIds = operator.dml()
.query(jsonbTableName)
.select("id")
.where(q -> q.where("data$exist", "name"))
.fetch(ResultWrappers.mapStream())
.sync()
.map(map -> String.valueOf(map.get("id")))
.collect(Collectors.toList());

List<String> anyIds = operator.dml()
.query(jsonbTableName)
.select("id")
.where(q -> q.where("data$exist$any", Arrays.asList("name", "status")))
.fetch(ResultWrappers.mapStream())
.sync()
.map(map -> String.valueOf(map.get("id")))
.collect(Collectors.toList());

List<String> allIds = operator.dml()
.query(jsonbTableName)
.select("id")
.where(q -> q.where("data$exist$all", Arrays.asList("name", "age")))
.fetch(ResultWrappers.mapStream())
.sync()
.map(map -> String.valueOf(map.get("id")))
.collect(Collectors.toList());

List<String> containsIds = operator.dml()
.query(jsonbTableName)
.select("id")
.where(q -> q.where("data$exist$contains", Collections.singletonMap("name", "JetLinks")))
.fetch(ResultWrappers.mapStream())
.sync()
.map(map -> String.valueOf(map.get("id")))
.collect(Collectors.toList());

LinkedHashMap<String, Object> containedTarget = new LinkedHashMap<>();
containedTarget.put("name", "JetLinks");
containedTarget.put("age", 18);
containedTarget.put("status", "ok");

List<String> containedIds = operator.dml()
.query(jsonbTableName)
.select("id")
.where(q -> q.where("data$exist$contained", containedTarget))
.fetch(ResultWrappers.mapStream())
.sync()
.map(map -> String.valueOf(map.get("id")))
.collect(Collectors.toList());

Assert.assertEquals(Arrays.asList("1", "2"), existsIds);
Assert.assertEquals(Arrays.asList("1", "2", "3"), anyIds);
Assert.assertEquals(List.of("1"), allIds);
Assert.assertEquals(List.of("1"), containsIds);
Assert.assertEquals(List.of("1"), containedIds);
} finally {
try {
operator.sql()
.sync()
.execute(SqlRequests.of("drop table if exists public." + jsonbTableName));
} catch (Exception ignore) {
}
}
}

@Before
public void createJsonbTable() {
RDBDatabaseMetadata database = getDatabase();
DatabaseOperator operator = DefaultDatabaseOperator.of(database);

operator.ddl()
.createOrAlter(jsonbTableName)
.addColumn().name("id").varchar(32).primaryKey().comment("ID").commit()
.addColumn().name("data").type(JsonbType.INSTANCE).comment("数据").commit()
.commit()
.sync();
}
}
Loading
Loading