123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- classdef (Sealed) labdb < handle
- % Class labdb
- % This is a wrapper class for the JDBC MySQL driver.
- % It has several features which are generally useful
- % 1. It reads credentials configurations from ~/.dbconf so that you don't have to type in credentials or store them in code.
- % 2. It maintains a single connection per configuration (a configuration is a user/hostname pair) for memory efficiency.
- % 3. It has several useful functions for getting and saving data to MySQL
- % 4. By default, automatically checks that the connection is alive and well before trying to communicate with the database.
- % This incurs a small overhead, and can be turned off with `obj.autocheck = false`
- %
- % To see a list of the functions for the class type db.labdb.help
- % To get help for a specific function call db.labdb.help(function_name), e.g. db.labdb.help('query')
-
- properties (SetAccess=public,GetAccess=protected)
- config = [];
- end
-
- properties (SetAccess=public,GetAccess=public)
- dbconn = [];
- autocheck = true;% Normally this class checks for DB connectivity before queries. If you are running many you can skip the autocheck.
- end
-
- methods (Access=private)
- function obj = labdb
- end
- end
- methods (Access=public)
- function setConfig(obj, config)
- % Manually set the user, passwd, host.
- % input should be a struct with those fields
- obj.config = config;
- end
-
- function host = getHost(obj)
- % host = getHost()
- % output: the hostname from the current config
- host = obj.config.host;
- end
-
- function user = getUser(obj)
- % host = getUser()
- % output: the user from the current config
- user = obj.config.user;
- end
-
- function out = getConnectionInfo(obj)
- % out = getConnectionInfo
- % returns a struct with connection information (driver version, connection URL, etc)
- out = ping(obj.dbconn);
- end
-
- function list = list_enums(obj, tablename, column)
- out = obj.query('show columns from %s where field="%s"',{tablename, column});
- enums = out.COLUMN_TYPE{1}(6:end-1);
- done = false;
- list = {};
- while ~done
- [this_one, enums] = strtok(enums, ',');
- list = [list, {strtrim(replace(this_one,'''',''))}];
- if isempty(enums)
- done = true;
- else
- enums = enums(2:end);
- end
- end
- end
- function cur = execute(obj, sqlstr, args)
- % cur = execute(sql_command, [input arguments])
- % executes the sql_command and returns a cursor object.
- % Place holders can be used using sprintf style syntax.
- % e.g. execute('insert into foo (a,b) values (%.3f,"%s")',{3.1441241, 'some text goes here'})
-
- if nargin<3
- args = {};
- end
- if obj.autocheck
- checkConnection(obj);
- end
- sqlquery = sprintf(sqlstr, args{:});
- cur = exec(obj.dbconn, sqlquery);
- if cur.Message
- % There was an error
- fprintf(2,'SQL ERROR: %s \n',cur.Message);
- end
- end
-
- function use(obj, schema)
- % use(schema)
- % sets the default schema
- cur = execute(obj,sprintf('use %s', schema));
- if cur.Message
- error('Failed to switch schemas')
- end
- end
- function out = explain(obj, table)
- % explain(something)
- % a shortcut for 'explain ...'
- out = query(obj,sprintf('explain %s', table));
- end
- function out = last_insert_id(obj)
- % out = last_insert_id
- % returns the last_insert_id
- out = query(obj,'select last_insert_id() as id');
- out = out.id;
- end
- function varargout = get(obj, sqlstr, args)
- % varargout = get(sql_command, [input arguments])
- % Like query, this command uses sprintf style parsing to execute a MySQL SELECT command.
- % However, get is special in that it returns one variable for each column in the SELECT
- % whereas query returns a single table for the entire query.
- %
- % e.g. sessid = obj.get('select sessid from sessions limit 1') % sessid will be a float
- % [sessdate, sessid] = obj.get('select sessiondate, sessid from sessions') % sessdate will be a cell array and sessid will be a vector of float
-
- if nargin < 3
- args = {};
- end
- out = query(obj,sqlstr,args);
- varargout = cell(1,nargout);
- if isempty(out)
- return;
- end
-
- for vx = 1:nargout
- varargout{vx} = out.(out.Properties.VariableNames{vx});
- end
- end
-
- function call(obj, sqlstr, args)
- % call('storedProcedure(2456)')
- % call('storedProcedure(%d,"%s")',{1234,'stuff'})
- % Calls the stored procedure with the passed arguments.
-
- if nargin<3
- args={};
- end
- execute(obj,sprintf('call %s', sprintf(sqlstr, args{:})));
- end
-
- function out = query(obj, sqlstr, args)
- % tableout = query(sql_command, [input arguments])
- % Like execute, this command uses sprintf style parsing. But instead of returning a cursor,
- % query returns a `table` object.
- %
- % e.g. sessid = obj.query('select sessid from sessions limit 1') % sessid will be a table with a sessid column.
- % [sessdate, sessid] = obj.get('select sessiondate, sessid from sessions') % sessdate will be a cell array and sessid will be a vector of float
-
- if obj.autocheck
- checkConnection(obj);
- end
-
- if nargin < 3
- args = {};
- end
-
- sqlquery = sprintf(sqlstr, args{:});
- cur = exec(obj.dbconn, sqlquery);
- if cur.Message
- % There was an error
- fprintf(2,'SQL ERROR: %s \n',cur.Message);
- out = [];
- else
- data = fetch(cur);
- if cur.rows <= 0
- out = {};
- else
- out = data.Data;
- end
- end
- close(cur);
-
- end
-
- function saveData(obj, tablename, data, varargin)
- % saveData(obj, tablename, data, colnames)
- if obj.autocheck
- checkConnection(obj);
- end
- if nargin < 4
- if isstruct(data)
- colnames = fields(data);
- data = struct2table(data,'AsArray',true);
- elseif istable(data)
- colnames = data.Properties.VariableNames;
- else
- error('labdb:saveData','Must specify column names if not using table or struct type')
- end
- end
-
- datainsert(obj.dbconn, tablename, colnames, data);
-
- end
-
- function ok = isopen(obj)
- try
- cur = obj.dbconn.exec('select 1 from dual');
- assert(isempty(cur.Message));
- ok = true;
- catch me
- ok = false;
- end
-
- end
-
- function checkConnection(obj)
-
-
- try
- getId(obj.dbconn.Handle);
- cur = obj.dbconn.exec('select 1 from dual');
- assert(isempty(cur.Message));
- catch
- obj.dbconn = [];
- end
- if isempty(obj.dbconn) || ~obj.dbconn.isopen
- obj.dbconn = database(obj.config.db,obj.config.user,obj.config.passwd,'Vendor','MySQL',...
- 'Server',obj.config.host,'PortNumber',obj.config.port);
- end
-
-
- if ~isempty(obj.dbconn.Message)
- fprintf(2,'%s\n',obj.dbconn.Message);
- obj.dbconn = [];
- end
- end
-
- function close(obj)
- close(obj.dbconn);
- obj.dbconn = [];
- end
-
- end
-
- methods (Static)
- % We use a static method to give the matlab client a database object
- % for each configuration (IP, username) we only make one connection and then re-use it.
- % This is ok for MATLAB since it is single threaded.
- % It could potentially cause strange behavior if a user was doing inserts in a timer and also in the main
- % thread and using `last_insert_id`
- function help(fname)
- if nargin == 0
- help('db.labdb')
- methods('db.labdb')
- else
- help(sprintf('db.labdb.%s',fname))
- end
- end
- function so = getConnection(varargin)
- setdbprefs('DataReturnFormat','table')
- addMysqlConnecterToPath(); % Make sure the driver is on the path.
-
- persistent localObj; % This is where we store existing connections.
- if nargin == 1
- % The user provided a config name, so use that.
- configsec = varargin{1};
- elseif nargin ==0
- % The user provided nothing. Use the default config.
- configsec = 'client';
- else
- configsec = utils.inputordefault('config','client',varargin);
- end
- % Check if we have a connection with the right name.
- try
- so = localObj.(configsec);
- return;
- catch ME
- if ~strcmp(ME.identifier, {'MATLAB:nonExistentField', 'MATLAB:structRefFromNonStruct'})
- rethrow(ME)
- end
- end
- % No connection exists
- localObj.(configsec) = [];
- if nargin < 3
- config = readDBconf(configsec);
- else
- config.host = varargin{1};
- config.user = varargin{2};
- config.passwd = varargin{3};
- if nargin > 3
- config.db = varargin{4};
- else
- config.db = 'met';
- end
-
- end
-
- localObj.(configsec) = db.labdb;
- setConfig(localObj.(configsec), config);
- checkConnection(localObj.(configsec));
- so = localObj.(configsec);
- end
- end
- end
- function cfg = readDBconf(cfgname)
- % A private function to help the labdb class read credentials from the
- % .dbconf file in the user's home directory.
- def.db = ''; % In case db is not passed in set it by default to nothing.
- def.port = 3306;
- if nargin == 0
-
- cfgname = 'client';
- end
- cfgfile = '~/.dbconf';
- if ~exist(cfgfile,'file')
- try
- cfgfile = fullfile(getenv('USERPROFILE'),'.dbconf');
- catch
- error('labdb:dbconf','.dbconf file not found in home directory');
- end
- end
- allcfg = utils.ini2struct(cfgfile);
- fopts = allcfg.(cfgname);
- cfg = utils.apply_struct(def, fopts);
- end
- function addMysqlConnecterToPath()
- jcp = javaclasspath('-all');
-
- jarfile = 'mysql-connector-java-5.1.42-bin.jar';
-
- if isempty(cell2mat(regexp(jcp,jarfile)))
- % Mysql is not on the path
- this_file = mfilename('fullpath');
- [this_path] = fileparts(this_file);
- javaaddpath(fullfile(this_path, jarfile));
- end
-
- end
|