<commit_before>package com.laytonsmith.persistence;

import com.laytonsmith.PureUtilities.Common.StringUtils;
import com.laytonsmith.PureUtilities.DaemonManager;
import com.laytonsmith.annotations.datasource;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.persistence.io.ConnectionMixin;
import com.laytonsmith.persistence.io.ConnectionMixinFactory;
import java.io.IOException;
import java.net.URI;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author lsmith
 */
@datasource("sqlite")
public class SQLiteDataSource extends SQLDataSource {
	
	/* These values may not be changed without creating an upgrade routine */
	private static final String TABLE_NAME = "persistance"; //Note the misspelling!
	private String path;
	private ConnectionMixin mixin;
	
	private SQLiteDataSource(){
		
	}
	
	public SQLiteDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{
		super(uri, options);		
		mixin = getConnectionMixin();		
		try {
			Class.forName(org.sqlite.JDBC.class.getName());
			path = mixin.getPath();
			connect();
			Statement statement = getConnection().createStatement();
			statement.executeUpdate(getTableCreationQuery());
			updateLastConnected();
		} catch (ClassNotFoundException | UnsupportedOperationException | IOException | SQLException ex) {
			throw new DataSourceException("An error occured while setting up a connection to the SQLite database", ex);
		} 
	}
	
	/**
	 * {@inheritDoc}
	 * 
	 * SQLite connections support INSERT OR REPLACE, which prevents duplicate keys from mattering, so this method needs to be overridden for
	 * SQLite.
	 * @param dm
	 * @param key
	 * @param value
	 * @return
	 * @throws ReadOnlyException
	 * @throws DataSourceException
	 * @throws IOException 
	 */
	@Override
	public boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException {
		try {
			connect();
			if(value == null){
				clearKey0(dm, key);
			} else {
				PreparedStatement statement = getConnection().prepareStatement("INSERT OR REPLACE INTO `" + TABLE_NAME 
						+ "` (`" + getKeyColumn() + "`, `" + getValueColumn() + "`) VALUES (?, ?)");
				statement.setString(1, StringUtils.Join(key, "."));
				statement.setString(2, value);
				statement.executeUpdate();
			}
			updateLastConnected();
			return true;
		} catch (SQLException ex) {
			throw new DataSourceException(ex.getMessage(), ex);
		}
	}

	@Override
	public String docs() {
		return "SQLite {sqlite://path/to/db/file.db} This type store data in a SQLite database."
			+ " All the pros and cons of MySQL apply here. The database will contain a lone table,"
				+ " and the table should be created with the query: <syntaxhighlight lang=\"sql\">"
				+ getTableCreationQuery() + "</syntaxhighlight>";
	}
	
	/**
	 * Returns the table creation query that should be used to create the table specified.
	 * This is public for documentation, but is used internally.
	 * @param table
	 * @return 
	 */
	public final String getTableCreationQuery(){
		return "CREATE TABLE IF NOT EXISTS `" + TABLE_NAME + "` (`" + getKeyColumn() + "` TEXT PRIMARY KEY,"
					+ " `" + getValueColumn() + "` TEXT)";
	}

	@Override
	public CHVersion since() {
		return CHVersion.V3_3_1;
	}

	@Override
	protected void startTransaction0(DaemonManager dm) {
		try {
			getConnection().prepareStatement("BEGIN TRANSACTION").execute();
			updateLastConnected();
		} catch (SQLException ex) {
			Logger.getLogger(SQLiteDataSource.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	@Override<commit_msg>Get an exclusive lock for SQLite writes. Speculative fix.<commit_after>package com.laytonsmith.persistence;

import com.laytonsmith.PureUtilities.Common.StringUtils;
import com.laytonsmith.PureUtilities.DaemonManager;
import com.laytonsmith.annotations.datasource;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.persistence.io.ConnectionMixin;
import com.laytonsmith.persistence.io.ConnectionMixinFactory;
import java.io.IOException;
import java.net.URI;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author lsmith
 */
@datasource("sqlite")
public class SQLiteDataSource extends SQLDataSource {
	
	/* These values may not be changed without creating an upgrade routine */
	private static final String TABLE_NAME = "persistance"; //Note the misspelling!
	private String path;
	private ConnectionMixin mixin;
	
	private SQLiteDataSource(){
		
	}
	
	public SQLiteDataSource(URI uri, ConnectionMixinFactory.ConnectionMixinOptions options) throws DataSourceException{
		super(uri, options);		
		mixin = getConnectionMixin();		
		try {
			Class.forName(org.sqlite.JDBC.class.getName());
			path = mixin.getPath();
			connect();
			Statement statement = getConnection().createStatement();
			statement.executeUpdate(getTableCreationQuery());
			updateLastConnected();
		} catch (ClassNotFoundException | UnsupportedOperationException | IOException | SQLException ex) {
			throw new DataSourceException("An error occured while setting up a connection to the SQLite database", ex);
		} 
	}
	
	/**
	 * {@inheritDoc}
	 * 
	 * SQLite connections support INSERT OR REPLACE, which prevents duplicate keys from mattering, so this method needs to be overridden for
	 * SQLite.
	 * @param dm
	 * @param key
	 * @param value
	 * @return
	 * @throws ReadOnlyException
	 * @throws DataSourceException
	 * @throws IOException 
	 */
	@Override
	public boolean set0(DaemonManager dm, String[] key, String value) throws ReadOnlyException, DataSourceException, IOException {
		try {
			connect();
			if(value == null){
				clearKey0(dm, key);
			} else {
				startExclusiveTransaction();
				PreparedStatement statement = getConnection().prepareStatement("INSERT OR REPLACE INTO `" + TABLE_NAME 
						+ "` (`" + getKeyColumn() + "`, `" + getValueColumn() + "`) VALUES (?, ?)");
				statement.setString(1, StringUtils.Join(key, "."));
				statement.setString(2, value);
				statement.executeUpdate();
				stopExclusiveTransaction();
			}
			updateLastConnected();
			return true;
		} catch (SQLException ex) {
			rollbackExclusiveTransaction();
			throw new DataSourceException(ex.getMessage(), ex);
		}
	}

	@Override
	public String docs() {
		return "SQLite {sqlite://path/to/db/file.db} This type store data in a SQLite database."
			+ " All the pros and cons of MySQL apply here. The database will contain a lone table,"
				+ " and the table should be created with the query: <syntaxhighlight lang=\"sql\">"
				+ getTableCreationQuery() + "</syntaxhighlight>";
	}
	
	/**
	 * Returns the table creation query that should be used to create the table specified.
	 * This is public for documentation, but is used internally.
	 * @param table
	 * @return 
	 */
	public final String getTableCreationQuery(){
		return "CREATE TABLE IF NOT EXISTS `" + TABLE_NAME + "` (`" + getKeyColumn() + "` TEXT PRIMARY KEY,"
					+ " `" + getValueColumn() + "` TEXT)";
	}

	@Override
	public CHVersion since() {
		return CHVersion.V3_3_1;
	}
	
	private void startExclusiveTransaction() throws SQLException{
		getConnection().createStatement().execute("BEGIN EXCLUSIVE TRANSACTION");
	}
	
	private void stopExclusiveTransaction() throws SQLException{
		getConnection().createStatement().execute("COMMIT TRANSACTION");
	}
	
	private void rollbackExclusiveTransaction() {
		try {
			getConnection().createStatement().execute("ROLLBACK TRANSACTION");
		} catch (SQLException ex) {
			Logger.getLogger(SQLiteDataSource.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	@Override
	protected void startTransaction0(DaemonManager dm) {
		try {
			getConnection().prepareStatement("BEGIN TRANSACTION").execute();
			updateLastConnected();
		} catch (SQLException ex) {
			Logger.getLogger(SQLiteDataSource.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	@Override
{'fraction_non_alphanumeric': 0.11383516077368486, 'fraction_numerical': 0.001971171615128742, 'mean_word_length': 4.47403910991234, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '980684', 'n_tokens_mistral': 2615, 'n_tokens_neox': 2289, 'n_words': 631}