diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/HugeFactory.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/HugeFactory.java index 7eca2139c7..1f7530ae9e 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/HugeFactory.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/HugeFactory.java @@ -34,11 +34,19 @@ public class HugeFactory { + private static final String NAME_REGEX = "^[A-Za-z][A-Za-z0-9_]{0,47}$"; + private static final Map graphs = new HashMap<>(); public static synchronized HugeGraph open(Configuration config) { HugeConfig conf = new HugeConfig(config); String name = conf.get(CoreOptions.STORE); + E.checkArgument(name.matches(NAME_REGEX), + "Invalid graph name '%s', valid graph name is up to " + + "48 alpha-numeric characters and underscores " + + "and only letters are supported as first letter. " + + "Note: letter is case insensitive"); + name = name.toLowerCase(); HugeGraph graph = graphs.get(name); if (graph == null || graph.closed()) { graph = new HugeGraph(conf); diff --git a/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java b/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java index 8623df8bb6..0009f9918e 100644 --- a/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java +++ b/hugegraph-core/src/main/java/com/baidu/hugegraph/config/CoreOptions.java @@ -125,7 +125,7 @@ public static synchronized CoreOptions instance() { public static final ConfigOption VERTEX_CHECK_CUSTOMIZED_ID_EXIST = new ConfigOption<>( - "vertex.check_customzied_id_exist", + "vertex.check_customized_id_exist", "Whether to check the vertices exist for those using " + "customized id strategy", disallowEmpty(), diff --git a/hugegraph-mysql/src/main/java/com/baidu/hugegraph/backend/store/mysql/MysqlSessions.java b/hugegraph-mysql/src/main/java/com/baidu/hugegraph/backend/store/mysql/MysqlSessions.java index 9411ed5f0e..0b9962b0e3 100644 --- a/hugegraph-mysql/src/main/java/com/baidu/hugegraph/backend/store/mysql/MysqlSessions.java +++ b/hugegraph-mysql/src/main/java/com/baidu/hugegraph/backend/store/mysql/MysqlSessions.java @@ -64,6 +64,10 @@ public String database() { return this.database; } + public String escapedDatabase() { + return MysqlUtil.escapeString(this.database()); + } + /** * Try connect with specified database, will not reconnect if failed * @throws SQLException if a database access error occurs @@ -86,9 +90,9 @@ protected boolean opened() { private Connection open(boolean autoReconnect) throws SQLException { String url = this.config.get(MysqlOptions.JDBC_URL); if (url.endsWith("/")) { - url = String.format("%s%s", url, this.database); + url = String.format("%s%s", url, this.database()); } else { - url = String.format("%s/%s", url, this.database); + url = String.format("%s/%s", url, this.database()); } int maxTimes = this.config.get(MysqlOptions.JDBC_RECONNECT_MAX_TIMES); @@ -146,15 +150,15 @@ public void checkSessionConnected() { public void createDatabase() { // Create database with non-database-session - LOG.debug("Create database: {}", this.database); + LOG.debug("Create database: {}", this.database()); - String sql = this.buildCreateDatabase(this.database); + String sql = this.buildCreateDatabase(this.database()); try (Connection conn = this.openWithoutDB(0)) { conn.createStatement().execute(sql); } catch (SQLException e) { if (!e.getMessage().endsWith("already exists")) { throw new BackendException("Failed to create database '%s'", e, - this.database); + this.database()); } // Ignore exception if database already exists } @@ -167,28 +171,31 @@ protected String buildCreateDatabase(String database) { } public void dropDatabase() { - LOG.debug("Drop database: {}", this.database); + LOG.debug("Drop database: {}", this.database()); - String sql = String.format("DROP DATABASE IF EXISTS %s;", - this.database); + String sql = this.buildDropDatabase(this.database()); try (Connection conn = this.openWithoutDB(DROP_DB_TIMEOUT)) { conn.createStatement().execute(sql); } catch (SQLException e) { if (e.getCause() instanceof SocketTimeoutException) { - LOG.warn("Drop database '{}' timeout", this.database); + LOG.warn("Drop database '{}' timeout", this.database()); } else { throw new BackendException("Failed to drop database '%s'", - this.database); + this.database()); } } } + protected String buildDropDatabase(String database) { + return String.format("DROP DATABASE IF EXISTS %s;", database); + } + public boolean existsDatabase() { try (Connection conn = this.openWithoutDB(0); ResultSet result = conn.getMetaData().getCatalogs()) { while (result.next()) { String dbName = result.getString(1); - if (dbName.equals(this.database)) { + if (dbName.equals(this.database())) { return true; } } @@ -308,6 +315,14 @@ public void end() throws SQLException { this.conn.setAutoCommit(true); } + public void endAndLog() { + try { + this.conn.setAutoCommit(true); + } catch (SQLException e) { + LOG.warn("Failed to set connection to auto-commit status", e); + } + } + @Override public Integer commit() { int updated = 0; @@ -319,11 +334,8 @@ public Integer commit() { this.clear(); } catch (SQLException e) { throw new BackendException("Failed to commit", e); - } finally { - try { - this.end(); - } catch (SQLException ignored) {} } + this.endAndLog(); return updated; } @@ -335,9 +347,7 @@ public void rollback() { } catch (SQLException e) { throw new BackendException("Failed to rollback", e); } finally { - try { - this.end(); - } catch (SQLException ignored) {} + this.endAndLog(); } } @@ -352,6 +362,13 @@ public ResultSet select(String sql) throws SQLException { } public boolean execute(String sql) throws SQLException { + /* + * commit() or rollback() failed to set connection to auto-commit + * status in prior transaction. Manually set to auto-commit here. + */ + if (this.conn.getAutoCommit()) { + this.end(); + } return this.conn.createStatement().execute(sql); } diff --git a/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSerializer.java b/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSerializer.java index 4a787e35c9..d133ee7a31 100644 --- a/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSerializer.java +++ b/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSerializer.java @@ -19,12 +19,8 @@ package com.baidu.hugegraph.backend.store.postgresql; -import java.sql.SQLException; - import org.apache.logging.log4j.util.Strings; -import org.postgresql.core.Utils; -import com.baidu.hugegraph.HugeException; import com.baidu.hugegraph.backend.id.IdUtil; import com.baidu.hugegraph.backend.serializer.TableBackendEntry; import com.baidu.hugegraph.backend.store.BackendEntry; @@ -62,14 +58,6 @@ protected String escapeString(String value) { if (value.equals("\u0000")) { return "\'\'"; } - StringBuilder builder = new StringBuilder(8 + value.length()); - builder.append('\''); - try { - Utils.escapeLiteral(builder, value, false); - } catch (SQLException e) { - throw new HugeException("Failed to escape '%s'", e, value); - } - builder.append('\''); - return builder.toString(); + return PostgresqlSessions.escapeString(value); } } diff --git a/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSessions.java b/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSessions.java index fb882b7db2..0927db8569 100644 --- a/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSessions.java +++ b/hugegraph-postgresql/src/main/java/com/baidu/hugegraph/backend/store/postgresql/PostgresqlSessions.java @@ -20,12 +20,15 @@ package com.baidu.hugegraph.backend.store.postgresql; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; import org.apache.http.client.utils.URIBuilder; +import org.postgresql.core.Utils; import org.postgresql.util.PSQLException; import org.slf4j.Logger; +import com.baidu.hugegraph.HugeException; import com.baidu.hugegraph.backend.BackendException; import com.baidu.hugegraph.backend.store.mysql.MysqlSessions; import com.baidu.hugegraph.backend.store.mysql.MysqlStore; @@ -45,6 +48,20 @@ public PostgresqlSessions(HugeConfig config, String database, String store) { super(config, database, store); } + @Override + public boolean existsDatabase() { + String statement = String.format( + "SELECT datname FROM pg_catalog.pg_database " + + "WHERE datname = %s;", this.escapedDatabase()); + try (Connection conn = this.openWithoutDB(0)) { + ResultSet result = conn.createStatement().executeQuery(statement); + return result.next(); + } catch (Exception e) { + throw new BackendException("Failed to obtain PostgreSQL metadata," + + " please ensure it is ok", e); + } + } + @Override public void createDatabase() { // Create database with non-database-session @@ -76,9 +93,32 @@ protected String buildCreateDatabase(String database) { return String.format(POSTGRESQL_DB_CREATE, database); } + @Override + protected String buildDropDatabase(String database) { + return String.format( + "REVOKE CONNECT ON DATABASE %s FROM public;" + + "SELECT pg_terminate_backend(pg_stat_activity.pid) " + + " FROM pg_stat_activity " + + " WHERE pg_stat_activity.datname = %s;" + + "DROP DATABASE IF EXISTS %s;", + database, escapeString(database), database); + } + @Override protected URIBuilder newConnectionURIBuilder() { // Suppress error log when database does not exist return new URIBuilder().addParameter("loggerLevel", "OFF"); } + + public static String escapeString(String value) { + StringBuilder builder = new StringBuilder(8 + value.length()); + builder.append('\''); + try { + Utils.escapeLiteral(builder, value, false); + } catch (SQLException e) { + throw new HugeException("Failed to escape '%s'", e, value); + } + builder.append('\''); + return builder.toString(); + } } diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/CoreTestSuite.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/CoreTestSuite.java index 0f1b713720..cc4ed05aef 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/CoreTestSuite.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/CoreTestSuite.java @@ -43,7 +43,8 @@ EdgeCoreTest.class, VertexPropertyCoreTest.class, EdgePropertyCoreTest.class, - RestoreCoreTest.class + RestoreCoreTest.class, + MultiGraphsTest.class }) public class CoreTestSuite { diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/core/MultiGraphsTest.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/MultiGraphsTest.java new file mode 100644 index 0000000000..c8f8a975cc --- /dev/null +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/core/MultiGraphsTest.java @@ -0,0 +1,177 @@ +/* + * Copyright 2017 HugeGraph Authors + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package com.baidu.hugegraph.core; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.configuration.BaseConfiguration; +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.apache.tinkerpop.gremlin.structure.util.GraphFactory; +import org.junit.Test; + +import com.baidu.hugegraph.HugeGraph; +import com.baidu.hugegraph.backend.id.IdGenerator; +import com.baidu.hugegraph.config.CoreOptions; +import com.baidu.hugegraph.testutil.Assert; +import com.baidu.hugegraph.testutil.Utils; + +import jersey.repackaged.com.google.common.collect.ImmutableList; + +public class MultiGraphsTest { + + private static final String NAME48 = + "g12345678901234567890123456789012345678901234567"; + + @Test + public void testCreateMultiGraphs() { + List graphs = openGraphs("g_1", NAME48); + for (HugeGraph graph : graphs) { + graph.initBackend(); + graph.clearBackend(); + } + destoryGraphs(graphs); + } + + @Test + public void testCreateGraphsWithInvalidNames() { + List invalidNames = ImmutableList.of( + "", " ", " g", "g 1", " .", ". .", + "@", "$", "%", "^", "&", "*", "(", ")", + "_", "+", "`", "-", "=", "{", "}", "|", + "[", "]", "\"", "<", "?", ";", "'", "~", + ",", ".", "/", "\\", + "~g", "g~", "g'", + "_1", "_a", + "1a", "123", + NAME48 + "8"); + for (String name : invalidNames) { + Assert.assertThrows(RuntimeException.class, () -> openGraphs(name)); + } + } + + @Test + public void testCreateGraphsWithSameName() { + List graphs = openGraphs("g", "g", "G"); + HugeGraph g1 = graphs.get(0); + HugeGraph g2 = graphs.get(1); + HugeGraph g3 = graphs.get(2); + + g1.initBackend(); + g2.initBackend(); + g3.initBackend(); + + Assert.assertThrows(IllegalArgumentException.class, + () -> g2.vertexLabel("node")); + Assert.assertThrows(IllegalArgumentException.class, + () -> g3.vertexLabel("node")); + g1.schema().vertexLabel("node").useCustomizeNumberId() + .ifNotExist().create(); + g2.vertexLabel("node"); + g3.vertexLabel("node"); + + g1.addVertex(T.label, "node", T.id, 1); + g1.tx().commit(); + Iterator vertices = g2.vertices(1); + Assert.assertTrue(vertices.hasNext()); + Vertex vertex = vertices.next(); + Assert.assertFalse(vertices.hasNext()); + Assert.assertEquals(IdGenerator.of(1), vertex.id()); + + vertices = g3.vertices(1); + Assert.assertTrue(vertices.hasNext()); + vertex = vertices.next(); + Assert.assertFalse(vertices.hasNext()); + Assert.assertEquals(IdGenerator.of(1), vertex.id()); + + g1.clearBackend(); + g2.clearBackend(); + g3.clearBackend(); + destoryGraphs(ImmutableList.of(g1)); + } + + @Test + public void testCreateGraphWithSameNameDifferentBackends() { + HugeGraph g1 = openGraphWithBackend("graph", "memory", "text"); + g1.initBackend(); + Assert.assertThrows(RuntimeException.class, + () -> openGraphWithBackend("graph", "rocksdb", + "binary")); + g1.clearBackend(); + g1.close(); + } + + @Test + public void testCreateGraphsWithDifferentNameDifferentBackends() { + HugeGraph g1 = openGraphWithBackend("g1", "memory", "text"); + HugeGraph g2 = openGraphWithBackend("g2", "rocksdb", "binary"); + HugeGraph graph = openGraphs("graph").get(0); + g1.initBackend(); + g2.initBackend(); + graph.initBackend(); + + g1.clearBackend(); + g2.clearBackend(); + graph.clearBackend(); + + destoryGraphs(ImmutableList.of(g1, g2, graph)); + } + + public static List openGraphs(String... graphNames) { + List graphs = new ArrayList<>(graphNames.length); + PropertiesConfiguration conf = Utils.getConf(); + Configuration config = new BaseConfiguration(); + for (Iterator keys = conf.getKeys(); keys.hasNext();) { + String key = keys.next(); + config.setProperty(key, conf.getProperty(key)); + } + ((BaseConfiguration) config).setDelimiterParsingDisabled(true); + for (String graphName : graphNames) { + config.setProperty(CoreOptions.STORE.name(), graphName); + graphs.add((HugeGraph) GraphFactory.open(config)); + } + return graphs; + } + + public static void destoryGraphs(List graphs) { + for (HugeGraph graph : graphs) { + graph.close(); + } + } + + public static HugeGraph openGraphWithBackend(String graph, String backend, + String serializer) { + PropertiesConfiguration conf = Utils.getConf(); + Configuration config = new BaseConfiguration(); + for (Iterator keys = conf.getKeys(); keys.hasNext();) { + String key = keys.next(); + config.setProperty(key, conf.getProperty(key)); + } + ((BaseConfiguration) config).setDelimiterParsingDisabled(true); + config.setProperty(CoreOptions.STORE.name(), graph); + config.setProperty(CoreOptions.BACKEND.name(), backend); + config.setProperty(CoreOptions.SERIALIZER.name(), serializer); + return ((HugeGraph) GraphFactory.open(config)); + } +} diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/testutil/Utils.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/testutil/Utils.java index c185d1d557..3883495870 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/testutil/Utils.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/testutil/Utils.java @@ -19,18 +19,23 @@ package com.baidu.hugegraph.testutil; +import java.io.File; import java.util.Date; import java.util.List; +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Vertex; +import com.baidu.hugegraph.HugeException; import com.baidu.hugegraph.HugeFactory; import com.baidu.hugegraph.HugeGraph; import com.baidu.hugegraph.backend.id.Id; import com.baidu.hugegraph.testutil.FakeObjects.FakeEdge; import com.baidu.hugegraph.testutil.FakeObjects.FakeVertex; import com.baidu.hugegraph.util.DateUtil; +import com.baidu.hugegraph.util.E; public class Utils { @@ -80,4 +85,22 @@ public static boolean contains(List edges, FakeEdge fakeEdge) { public static Date date(String rawDate) { return DateUtil.parse(rawDate); } + + public static PropertiesConfiguration getConf() { + String confFile = Utils.class.getClassLoader() + .getResource(CONF_PATH).getPath(); + File file = new File(confFile); + E.checkArgument(file.exists() && file.isFile() && file.canRead(), + "Need to specify a readable config file rather than:" + + " %s", file.toString()); + + PropertiesConfiguration config; + try { + config = new PropertiesConfiguration(file); + } catch (ConfigurationException e) { + throw new HugeException("Unable to load config file: %s", + e, confFile); + } + return config; + } } diff --git a/hugegraph-test/src/main/java/com/baidu/hugegraph/tinkerpop/TestGraphProvider.java b/hugegraph-test/src/main/java/com/baidu/hugegraph/tinkerpop/TestGraphProvider.java index 9cb8332059..db5e5543cf 100644 --- a/hugegraph-test/src/main/java/com/baidu/hugegraph/tinkerpop/TestGraphProvider.java +++ b/hugegraph-test/src/main/java/com/baidu/hugegraph/tinkerpop/TestGraphProvider.java @@ -57,6 +57,7 @@ import com.baidu.hugegraph.structure.HugeProperty; import com.baidu.hugegraph.structure.HugeVertex; import com.baidu.hugegraph.structure.HugeVertexProperty; +import com.baidu.hugegraph.testutil.Utils; import com.baidu.hugegraph.type.define.IdStrategy; import com.baidu.hugegraph.util.E; import com.baidu.hugegraph.util.Log; @@ -119,7 +120,7 @@ public TestGraphProvider(String suite) throws IOException { } private void initBlackList() throws IOException { - String filter = getConf().getString(FILTER); + String filter = Utils.getConf().getString(FILTER); if (filter == null || filter.isEmpty()) { filter = DEFAULT_FILTER; } @@ -157,25 +158,6 @@ private void initBlackList() throws IOException { } } - private static PropertiesConfiguration getConf() { - String confFile = TestGraphProvider.class.getClassLoader() - .getResource(CONF_PATH).getPath(); - File file = new File(confFile); - E.checkArgument( - file.exists() && file.isFile() && file.canRead(), - "Need to specify a readable config file rather than: %s", - file.toString()); - - PropertiesConfiguration config; - try { - config = new PropertiesConfiguration(file); - } catch (ConfigurationException e) { - throw new HugeException("Unable to load config file: %s", - e, confFile); - } - return config; - } - @Override public Map getBaseConfiguration( String graphName, @@ -195,7 +177,7 @@ public Map getBaseConfiguration( LOG.debug("Full name of test is: {}", testFullName); LOG.debug("Prefix of test is: {}", testFullName.substring(0, index)); HashMap confMap = new HashMap<>(); - PropertiesConfiguration config = getConf(); + PropertiesConfiguration config = Utils.getConf(); Iterator keys = config.getKeys(); while (keys.hasNext()) { String key = keys.next();