Search in sources :

Example 1 with Session

use of com.mysql.cj.Session in project JavaSegundasQuintas by ecteruel.

the class ConnectionRegressionTest method testBug75592.

/**
 * Tests fix for BUG#75592 - "SHOW VARIABLES WHERE" is expensive.
 *
 * @throws Exception
 */
@Test
public void testBug75592() throws Exception {
    if (versionMeetsMinimum(5, 0, 3)) {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), "DISABLED");
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug75592QueryInterceptor.class.getName());
        JdbcConnection con = (JdbcConnection) getConnectionWithProps(props);
        // reference values
        Map<String, String> serverVariables = new HashMap<>();
        this.rs = con.createStatement().executeQuery("SHOW VARIABLES");
        while (this.rs.next()) {
            String val = this.rs.getString(2);
            serverVariables.put(this.rs.getString(1), "utf8mb3".equals(val) ? "utf8" : val);
        }
        // fix the renaming of "tx_isolation" to "transaction_isolation" that is made in NativeSession.loadServerVariables().
        if (!serverVariables.containsKey("transaction_isolation") && serverVariables.containsKey("tx_isolation")) {
            serverVariables.put("transaction_isolation", serverVariables.remove("tx_isolation"));
        }
        Session session = con.getSession();
        // check values from "select @@var..."
        assertEquals(serverVariables.get("auto_increment_increment"), session.getServerSession().getServerVariable("auto_increment_increment"));
        assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_CLIENT), session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_CLIENT));
        assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_CONNECTION), session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_CONNECTION));
        // we override character_set_results sometimes when configuring client charsets, thus need to check against actual value
        if (session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_RESULTS) == null) {
            assertEquals("", serverVariables.get(CharsetSettings.CHARACTER_SET_RESULTS));
        } else {
            assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_RESULTS), session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_RESULTS));
        }
        assertEquals(serverVariables.get("character_set_server"), session.getServerSession().getServerVariable("character_set_server"));
        assertEquals(serverVariables.get("init_connect"), session.getServerSession().getServerVariable("init_connect"));
        assertEquals(serverVariables.get("interactive_timeout"), session.getServerSession().getServerVariable("interactive_timeout"));
        assertEquals(serverVariables.get("license"), session.getServerSession().getServerVariable("license"));
        assertEquals(serverVariables.get("lower_case_table_names"), session.getServerSession().getServerVariable("lower_case_table_names"));
        assertEquals(serverVariables.get("max_allowed_packet"), session.getServerSession().getServerVariable("max_allowed_packet"));
        assertEquals(serverVariables.get("net_write_timeout"), session.getServerSession().getServerVariable("net_write_timeout"));
        if (!con.getServerVersion().meetsMinimum(new ServerVersion(8, 0, 3))) {
            assertEquals(serverVariables.get("query_cache_size"), session.getServerSession().getServerVariable("query_cache_size"));
            assertEquals(serverVariables.get("query_cache_type"), session.getServerSession().getServerVariable("query_cache_type"));
        }
        // not necessarily contains STRICT_TRANS_TABLES
        for (String sm : serverVariables.get("sql_mode").split(",")) {
            if (!sm.equals("STRICT_TRANS_TABLES")) {
                assertTrue(session.getServerSession().getServerVariable("sql_mode").contains(sm));
            }
        }
        assertEquals(serverVariables.get("system_time_zone"), session.getServerSession().getServerVariable("system_time_zone"));
        assertEquals(serverVariables.get("time_zone"), session.getServerSession().getServerVariable("time_zone"));
        assertEquals(serverVariables.get("transaction_isolation"), session.getServerSession().getServerVariable("transaction_isolation"));
        assertEquals(serverVariables.get("wait_timeout"), session.getServerSession().getServerVariable("wait_timeout"));
        if (!versionMeetsMinimum(5, 5, 0)) {
            assertEquals(serverVariables.get("language"), session.getServerSession().getServerVariable("language"));
        }
    }
}
Also used : ServerVersion(com.mysql.cj.ServerVersion) HashMap(java.util.HashMap) JdbcConnection(com.mysql.cj.jdbc.JdbcConnection) Properties(java.util.Properties) NativeServerSession(com.mysql.cj.protocol.a.NativeServerSession) NativeSession(com.mysql.cj.NativeSession) ServerSession(com.mysql.cj.protocol.ServerSession) Session(com.mysql.cj.Session) Test(org.junit.jupiter.api.Test)

Example 2 with Session

use of com.mysql.cj.Session in project ABC by RuiPinto96274.

the class ConnectionRegressionTest method testBug25642226.

/**
 * Tests fix for Bug#25642226, CHANGEUSER() NOT SETTING THE DATABASE PROPERLY WITH SHA USER.
 *
 * @throws Exception
 */
@Test
public void testBug25642226() throws Exception {
    assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, "This test requires server with SSL support.");
    assumeTrue(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 6, 5), "Requires MySQL 5.6.5+ server.");
    assumeTrue(pluginIsActive(this.stmt, "sha256_password"), "sha256_password required to run this test");
    assumeTrue(supportsTestCertificates(this.stmt), "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs");
    String pwd = "\u4F5C\u4F5C\u4F5C";
    final Properties props = new Properties();
    props.setProperty(PropertyKey.sslMode.getKeyName(), "REQUIRED");
    props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore");
    props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password");
    props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8");
    Connection c1 = getConnectionWithProps(dbUrl, props);
    Connection c2 = null;
    Session sess = ((JdbcConnection) c1).getSession();
    Statement s1 = c1.createStatement();
    this.rs = s1.executeQuery("select database()");
    this.rs.next();
    String origDb = this.rs.getString(1);
    System.out.println("URL [" + dbUrl + "]");
    System.out.println("1. Original database [" + origDb + "]");
    try {
        // create user with required password and sha256_password auth
        if (!sess.versionMeetsMinimum(8, 0, 5)) {
            s1.executeUpdate("SET @current_old_passwords = @@global.old_passwords");
            s1.executeUpdate("SET GLOBAL old_passwords= 2");
            s1.executeUpdate("SET SESSION old_passwords= 2");
        }
        createUser(s1, "'Bug25642226u1'@'%'", "identified WITH sha256_password");
        s1.executeUpdate("grant all on *.* to 'Bug25642226u1'@'%'");
        s1.executeUpdate(sess.versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'Bug25642226u1'@'%' IDENTIFIED BY '" + pwd + "'" : "set password for 'Bug25642226u1'@'%' = PASSWORD('" + pwd + "')");
        s1.executeUpdate("flush privileges");
        c2 = getConnectionWithProps(dbUrl, props);
        Statement s2 = c2.createStatement();
        ((JdbcConnection) c2).changeUser("Bug25642226u1", pwd);
        this.rs = s2.executeQuery("select database()");
        this.rs.next();
        System.out.println("2. Database after sha256 changeUser [" + this.rs.getString(1) + "]");
        // was returning null for database name
        assertEquals(origDb, this.rs.getString(1));
        // was failing with exception
        this.rs = s2.executeQuery("show tables");
        // create user with required password and caching_sha2_password auth
        if (sess.versionMeetsMinimum(8, 0, 3)) {
            assertTrue(pluginIsActive(s1, "caching_sha2_password"), "caching_sha2_password required to run this test");
            // create user with required password and sha256_password auth
            createUser(s1, "'Bug25642226u2'@'%'", "identified WITH caching_sha2_password");
            s1.executeUpdate("grant all on *.* to 'Bug25642226u2'@'%'");
            s1.executeUpdate(sess.versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'Bug25642226u2'@'%' IDENTIFIED BY '" + pwd + "'" : "set password for 'Bug25642226u2'@'%' = PASSWORD('" + pwd + "')");
            s1.executeUpdate("flush privileges");
            ((JdbcConnection) c2).changeUser("Bug25642226u2", pwd);
            this.rs = s2.executeQuery("select database()");
            this.rs.next();
            System.out.println("3. Database after sha2 changeUser [" + this.rs.getString(1) + "]");
            // was returning null for database name
            assertEquals(origDb, this.rs.getString(1));
            // was failing with exception
            this.rs = s2.executeQuery("show tables");
        }
        c2.close();
    } finally {
        s1.executeUpdate("flush privileges");
        if (!sess.versionMeetsMinimum(8, 0, 5)) {
            s1.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords");
        }
        c1.close();
        if (c2 != null) {
            c2.close();
        }
    }
}
Also used : PreparedStatement(java.sql.PreparedStatement) Statement(java.sql.Statement) ServerPreparedStatement(com.mysql.cj.jdbc.ServerPreparedStatement) ClientPreparedStatement(com.mysql.cj.jdbc.ClientPreparedStatement) ReplicationConnection(com.mysql.cj.jdbc.ha.ReplicationConnection) MysqlPooledConnection(com.mysql.cj.jdbc.MysqlPooledConnection) SuspendableXAConnection(com.mysql.cj.jdbc.SuspendableXAConnection) Connection(java.sql.Connection) XAConnection(javax.sql.XAConnection) PooledConnection(javax.sql.PooledConnection) MysqlXAConnection(com.mysql.cj.jdbc.MysqlXAConnection) JdbcConnection(com.mysql.cj.jdbc.JdbcConnection) MysqlConnection(com.mysql.cj.MysqlConnection) MysqlConnection(com.mysql.cj.MysqlConnection) JdbcConnection(com.mysql.cj.jdbc.JdbcConnection) Properties(java.util.Properties) NativeServerSession(com.mysql.cj.protocol.a.NativeServerSession) NativeSession(com.mysql.cj.NativeSession) ServerSession(com.mysql.cj.protocol.ServerSession) Session(com.mysql.cj.Session) Test(org.junit.jupiter.api.Test)

Example 3 with Session

use of com.mysql.cj.Session in project aws-mysql-jdbc by awslabs.

the class ConnectionRegressionTest method testBug25642226.

/**
 * Tests fix for Bug#25642226, CHANGEUSER() NOT SETTING THE DATABASE PROPERLY WITH SHA USER.
 *
 * @throws Exception
 */
@Test
public void testBug25642226() throws Exception {
    assumeTrue((((MysqlConnection) this.conn).getSession().getServerSession().getCapabilities().getCapabilityFlags() & NativeServerSession.CLIENT_SSL) != 0, "This test requires server with SSL support.");
    assumeTrue(((MysqlConnection) this.conn).getSession().versionMeetsMinimum(5, 6, 5), "Requires MySQL 5.6.5+ server.");
    assumeTrue(pluginIsActive(this.stmt, "sha256_password"), "sha256_password required to run this test");
    assumeTrue(supportsTestCertificates(this.stmt), "This test requires the server configured with SSL certificates from ConnectorJ/src/test/config/ssl-test-certs");
    String pwd = "\u4F5C\u4F5C\u4F5C";
    final Properties props = new Properties();
    props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.REQUIRED.name());
    props.setProperty(PropertyKey.trustCertificateKeyStoreUrl.getKeyName(), "file:src/test/config/ssl-test-certs/ca-truststore");
    props.setProperty(PropertyKey.trustCertificateKeyStorePassword.getKeyName(), "password");
    props.setProperty(PropertyKey.characterEncoding.getKeyName(), "UTF-8");
    Connection c1 = getConnectionWithProps(dbUrl, props);
    Connection c2 = null;
    Session sess = ((JdbcConnection) c1).getSession();
    Statement s1 = c1.createStatement();
    this.rs = s1.executeQuery("select database()");
    this.rs.next();
    String origDb = this.rs.getString(1);
    System.out.println("URL [" + dbUrl + "]");
    System.out.println("1. Original database [" + origDb + "]");
    try {
        // create user with required password and sha256_password auth
        if (!sess.versionMeetsMinimum(8, 0, 5)) {
            s1.executeUpdate("SET @current_old_passwords = @@global.old_passwords");
            s1.executeUpdate("SET GLOBAL old_passwords= 2");
            s1.executeUpdate("SET SESSION old_passwords= 2");
        }
        createUser(s1, "'Bug25642226u1'@'%'", "identified WITH sha256_password");
        s1.executeUpdate("grant all on *.* to 'Bug25642226u1'@'%'");
        s1.executeUpdate(sess.versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'Bug25642226u1'@'%' IDENTIFIED BY '" + pwd + "'" : "set password for 'Bug25642226u1'@'%' = PASSWORD('" + pwd + "')");
        s1.executeUpdate("flush privileges");
        c2 = getConnectionWithProps(dbUrl, props);
        Statement s2 = c2.createStatement();
        ((JdbcConnection) c2).changeUser("Bug25642226u1", pwd);
        this.rs = s2.executeQuery("select database()");
        this.rs.next();
        System.out.println("2. Database after sha256 changeUser [" + this.rs.getString(1) + "]");
        // was returning null for database name
        assertEquals(origDb, this.rs.getString(1));
        // was failing with exception
        this.rs = s2.executeQuery("show tables");
        // create user with required password and caching_sha2_password auth
        if (sess.versionMeetsMinimum(8, 0, 3)) {
            assertTrue(pluginIsActive(s1, "caching_sha2_password"), "caching_sha2_password required to run this test");
            // create user with required password and sha256_password auth
            createUser(s1, "'Bug25642226u2'@'%'", "identified WITH caching_sha2_password");
            s1.executeUpdate("grant all on *.* to 'Bug25642226u2'@'%'");
            s1.executeUpdate(sess.versionMeetsMinimum(5, 7, 6) ? "ALTER USER 'Bug25642226u2'@'%' IDENTIFIED BY '" + pwd + "'" : "set password for 'Bug25642226u2'@'%' = PASSWORD('" + pwd + "')");
            s1.executeUpdate("flush privileges");
            ((JdbcConnection) c2).changeUser("Bug25642226u2", pwd);
            this.rs = s2.executeQuery("select database()");
            this.rs.next();
            System.out.println("3. Database after sha2 changeUser [" + this.rs.getString(1) + "]");
            // was returning null for database name
            assertEquals(origDb, this.rs.getString(1));
            // was failing with exception
            this.rs = s2.executeQuery("show tables");
        }
        c2.close();
    } finally {
        s1.executeUpdate("flush privileges");
        if (!sess.versionMeetsMinimum(8, 0, 5)) {
            s1.executeUpdate("SET GLOBAL old_passwords = @current_old_passwords");
        }
        c1.close();
        if (c2 != null) {
            c2.close();
        }
    }
}
Also used : PreparedStatement(java.sql.PreparedStatement) Statement(java.sql.Statement) ServerPreparedStatement(com.mysql.cj.jdbc.ServerPreparedStatement) ClientPreparedStatement(com.mysql.cj.jdbc.ClientPreparedStatement) ReplicationConnection(com.mysql.cj.jdbc.ha.ReplicationConnection) MysqlPooledConnection(com.mysql.cj.jdbc.MysqlPooledConnection) SuspendableXAConnection(com.mysql.cj.jdbc.SuspendableXAConnection) Connection(java.sql.Connection) XAConnection(javax.sql.XAConnection) PooledConnection(javax.sql.PooledConnection) MysqlXAConnection(com.mysql.cj.jdbc.MysqlXAConnection) JdbcConnection(com.mysql.cj.jdbc.JdbcConnection) MysqlConnection(com.mysql.cj.MysqlConnection) MysqlConnection(com.mysql.cj.MysqlConnection) JdbcConnection(com.mysql.cj.jdbc.JdbcConnection) Properties(java.util.Properties) NativeServerSession(com.mysql.cj.protocol.a.NativeServerSession) NativeSession(com.mysql.cj.NativeSession) ServerSession(com.mysql.cj.protocol.ServerSession) Session(com.mysql.cj.Session) Test(org.junit.jupiter.api.Test)

Example 4 with Session

use of com.mysql.cj.Session in project aws-mysql-jdbc by awslabs.

the class ConnectionRegressionTest method testBug75592.

/**
 * Tests fix for BUG#75592 - "SHOW VARIABLES WHERE" is expensive.
 *
 * @throws Exception
 */
@Test
public void testBug75592() throws Exception {
    if (versionMeetsMinimum(5, 0, 3)) {
        Properties props = new Properties();
        props.setProperty(PropertyKey.sslMode.getKeyName(), SslMode.DISABLED.name());
        props.setProperty(PropertyKey.allowPublicKeyRetrieval.getKeyName(), "true");
        props.setProperty(PropertyKey.queryInterceptors.getKeyName(), Bug75592QueryInterceptor.class.getName());
        JdbcConnection con = (JdbcConnection) getConnectionWithProps(props);
        // reference values
        Map<String, String> serverVariables = new HashMap<>();
        this.rs = con.createStatement().executeQuery("SHOW VARIABLES");
        while (this.rs.next()) {
            String val = this.rs.getString(2);
            serverVariables.put(this.rs.getString(1), "utf8mb3".equals(val) ? "utf8" : val);
        }
        // fix the renaming of "tx_isolation" to "transaction_isolation" that is made in NativeSession.loadServerVariables().
        if (!serverVariables.containsKey("transaction_isolation") && serverVariables.containsKey("tx_isolation")) {
            serverVariables.put("transaction_isolation", serverVariables.remove("tx_isolation"));
        }
        Session session = con.getSession();
        // check values from "select @@var..."
        assertEquals(serverVariables.get("auto_increment_increment"), session.getServerSession().getServerVariable("auto_increment_increment"));
        assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_CLIENT), session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_CLIENT));
        assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_CONNECTION), session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_CONNECTION));
        // we override character_set_results sometimes when configuring client charsets, thus need to check against actual value
        if (session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_RESULTS) == null) {
            assertEquals("", serverVariables.get(CharsetSettings.CHARACTER_SET_RESULTS));
        } else {
            assertEquals(serverVariables.get(CharsetSettings.CHARACTER_SET_RESULTS), session.getServerSession().getServerVariable(CharsetSettings.CHARACTER_SET_RESULTS));
        }
        assertEquals(serverVariables.get("character_set_server"), session.getServerSession().getServerVariable("character_set_server"));
        assertEquals(serverVariables.get("init_connect"), session.getServerSession().getServerVariable("init_connect"));
        assertEquals(serverVariables.get("interactive_timeout"), session.getServerSession().getServerVariable("interactive_timeout"));
        assertEquals(serverVariables.get("license"), session.getServerSession().getServerVariable("license"));
        assertEquals(serverVariables.get("lower_case_table_names"), session.getServerSession().getServerVariable("lower_case_table_names"));
        assertEquals(serverVariables.get("max_allowed_packet"), session.getServerSession().getServerVariable("max_allowed_packet"));
        assertEquals(serverVariables.get("net_write_timeout"), session.getServerSession().getServerVariable("net_write_timeout"));
        if (!con.getServerVersion().meetsMinimum(new ServerVersion(8, 0, 3))) {
            assertEquals(serverVariables.get("query_cache_size"), session.getServerSession().getServerVariable("query_cache_size"));
            assertEquals(serverVariables.get("query_cache_type"), session.getServerSession().getServerVariable("query_cache_type"));
        }
        // not necessarily contains STRICT_TRANS_TABLES
        for (String sm : serverVariables.get("sql_mode").split(",")) {
            if (!sm.equals("STRICT_TRANS_TABLES")) {
                assertTrue(session.getServerSession().getServerVariable("sql_mode").contains(sm));
            }
        }
        assertEquals(serverVariables.get("system_time_zone"), session.getServerSession().getServerVariable("system_time_zone"));
        assertEquals(serverVariables.get("time_zone"), session.getServerSession().getServerVariable("time_zone"));
        assertEquals(serverVariables.get("transaction_isolation"), session.getServerSession().getServerVariable("transaction_isolation"));
        assertEquals(serverVariables.get("wait_timeout"), session.getServerSession().getServerVariable("wait_timeout"));
        if (!versionMeetsMinimum(5, 5, 0)) {
            assertEquals(serverVariables.get("language"), session.getServerSession().getServerVariable("language"));
        }
    }
}
Also used : ServerVersion(com.mysql.cj.ServerVersion) HashMap(java.util.HashMap) JdbcConnection(com.mysql.cj.jdbc.JdbcConnection) Properties(java.util.Properties) NativeServerSession(com.mysql.cj.protocol.a.NativeServerSession) NativeSession(com.mysql.cj.NativeSession) ServerSession(com.mysql.cj.protocol.ServerSession) Session(com.mysql.cj.Session) Test(org.junit.jupiter.api.Test)

Example 5 with Session

use of com.mysql.cj.Session in project mall-cloud-2 by liuanxin.

the class ShowSql8Interceptor method preProcess.

@Override
public <T extends Resultset> T preProcess(Supplier<String> sql, Query query) {
    if (LogUtil.SQL_LOG.isDebugEnabled()) {
        String realSql = getRealSql(sql);
        if (U.isNotNull(realSql)) {
            Thread currentThread = Thread.currentThread();
            long current = System.currentTimeMillis();
            long counter = COUNTER.addAndGet(1);
            TIME_CACHE.put(currentThread, counter + TIME_SPLIT + current);
            String dataSource = "";
            if (U.isNotNull(query)) {
                Session session = query.getSession();
                if (U.isNotNull(session)) {
                    HostInfo hostInfo = session.getHostInfo();
                    if (U.isNotNull(hostInfo)) {
                        dataSource = ", 数据源: " + hostInfo.getHost() + ":" + hostInfo.getPort() + "/" + hostInfo.getDatabase();
                    }
                }
            }
            LogUtil.SQL_LOG.debug("计数: {}{}, sql: {}", counter, dataSource, realSql);
        }
    }
    return null;
}
Also used : HostInfo(com.mysql.cj.conf.HostInfo) ServerSession(com.mysql.cj.protocol.ServerSession) Session(com.mysql.cj.Session)

Aggregations

Session (com.mysql.cj.Session)7 ServerSession (com.mysql.cj.protocol.ServerSession)7 NativeSession (com.mysql.cj.NativeSession)6 JdbcConnection (com.mysql.cj.jdbc.JdbcConnection)6 NativeServerSession (com.mysql.cj.protocol.a.NativeServerSession)6 Properties (java.util.Properties)6 Test (org.junit.jupiter.api.Test)6 MysqlConnection (com.mysql.cj.MysqlConnection)3 ServerVersion (com.mysql.cj.ServerVersion)3 ClientPreparedStatement (com.mysql.cj.jdbc.ClientPreparedStatement)3 MysqlPooledConnection (com.mysql.cj.jdbc.MysqlPooledConnection)3 MysqlXAConnection (com.mysql.cj.jdbc.MysqlXAConnection)3 ServerPreparedStatement (com.mysql.cj.jdbc.ServerPreparedStatement)3 SuspendableXAConnection (com.mysql.cj.jdbc.SuspendableXAConnection)3 ReplicationConnection (com.mysql.cj.jdbc.ha.ReplicationConnection)3 Connection (java.sql.Connection)3 PreparedStatement (java.sql.PreparedStatement)3 Statement (java.sql.Statement)3 HashMap (java.util.HashMap)3 PooledConnection (javax.sql.PooledConnection)3