Changes from mwork PC.
PDP 2025 MPI analysis new focus on database and SQL
This commit is contained in:
@@ -45,15 +45,15 @@ classdef DBHandler < handle
|
||||
else
|
||||
error('DB seems to be corrupt')
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
function obj = refresh(obj)
|
||||
% Get table names and the first rows of each table to understand the structure
|
||||
obj.getTableNames();
|
||||
obj.getTables();
|
||||
obj.getDistinctValues();
|
||||
% Get table names and the first rows of each table to understand the structure
|
||||
obj.getTableNames();
|
||||
obj.getTables();
|
||||
obj.getDistinctValues();
|
||||
end
|
||||
|
||||
function obj = getTableNames(obj)
|
||||
@@ -187,10 +187,11 @@ classdef DBHandler < handle
|
||||
if isstruct(newRow)
|
||||
fields = fieldnames(newRow);
|
||||
emptyFields = structfun(@isempty,newRow);
|
||||
if sum(emptyFields)>0
|
||||
newRow.(fields{emptyFields==1}) = NaN;
|
||||
disp(['In Table: ',tableName,': ',fields{emptyFields==1},' was empty, is now NaN ',newRow.(fields{emptyFields==1})])
|
||||
|
||||
if sum(emptyFields) > 0
|
||||
emptyFieldNames = fields(emptyFields); % use () to get a cell array
|
||||
for idx = 1:numel(emptyFieldNames)
|
||||
newRow.(emptyFieldNames{idx}) = NaN;
|
||||
end
|
||||
end
|
||||
newRow = struct2table(newRow);
|
||||
end
|
||||
@@ -274,87 +275,168 @@ classdef DBHandler < handle
|
||||
end
|
||||
end
|
||||
|
||||
function addBEREntry(obj, berValue, occurrence, runID, ffe, dfe, mlse, pf, eqType, ffe_order, dfe_order, len_tr, mu_ffe, mu_dfe, mu_dc, comment)
|
||||
% addBEREntry Adds a BER entry linked to an existing or new Equalizer entry.
|
||||
% Usage:
|
||||
% addBEREntry(runID, eq, ffe, dfe, mlse, pf, eqType, ffe_order, dfe_order, len_tr, mu_ffe, mu_dfe, mu_dc, berValue, comment)
|
||||
|
||||
if isempty(pf)
|
||||
postfilter_taps = [];
|
||||
function resultID = addProcessingResult(obj, run_id, resultData, eqParamsData)
|
||||
% addProcessingResult Adds a processing result and links it to an EqualizerParameters entry.
|
||||
%
|
||||
% Inputs:
|
||||
% run_id: A run_id from the main table to connect the BER with.
|
||||
%
|
||||
% resultData: A struct with fields corresponding to the ProcessingResults table.
|
||||
%
|
||||
% eqParamsData: A struct with fields corresponding to the EqualizerParameters table,
|
||||
% except 'eq_id' and 'config_hash'. These fields are used to compute
|
||||
% a hash and check for an existing configuration.
|
||||
%
|
||||
% Output:
|
||||
% resultID: The result_id of the newly inserted ProcessingResults entry.
|
||||
|
||||
% 1. Compute hash for equalizer parameters
|
||||
jsonStr = jsonencode(eqParamsData);
|
||||
md = java.security.MessageDigest.getInstance('MD5');
|
||||
md.update(uint8(jsonStr));
|
||||
hashBytes = typecast(md.digest, 'uint8');
|
||||
hashStr = lower(dec2hex(hashBytes)');
|
||||
hashStr = lower(strtrim(hashStr(:)')); % Convert to a lowercase string
|
||||
|
||||
% Add hash to equalizer parameters
|
||||
eqParamsData.config_hash = hashStr;
|
||||
|
||||
% 2. Check if an equalizer configuration with the same hash exists
|
||||
queryStr = sprintf('SELECT eq_id FROM EqualizerParameters WHERE config_hash = ''%s''', eqParamsData.config_hash);
|
||||
existingEntry = obj.fetch(queryStr);
|
||||
|
||||
if ~isempty(existingEntry)
|
||||
% Use existing eq_id
|
||||
eq_id = existingEntry{1,1};
|
||||
else
|
||||
postfilter_taps = pf.burg_coeff;
|
||||
% Insert the new equalizer configuration and get its eq_id
|
||||
eq_id = obj.appendToTable('EqualizerParameters', eqParamsData);
|
||||
end
|
||||
|
||||
% Create equalizer data struct for searching and adding if necessary
|
||||
equalizerData = struct( ...
|
||||
'ffe', jsonencode(ffe), ...
|
||||
'dfe', jsonencode(dfe), ...
|
||||
'mlse', jsonencode(mlse), ...
|
||||
'pf', jsonencode(pf), ...
|
||||
'eq_type', string(eqType), ...
|
||||
'ffe_order', jsonencode(ffe_order), ...
|
||||
'dfe_order', jsonencode(dfe_order), ...
|
||||
'postfilter_taps',jsonencode(postfilter_taps),...
|
||||
'len_tr', len_tr, ...
|
||||
'mu_ffe', jsonencode(mu_ffe), ...
|
||||
'mu_dfe', mu_dfe, ...
|
||||
'mu_dc', mu_dc, ...
|
||||
'comment', comment ...
|
||||
);
|
||||
% 3. Add the equalizer configuration reference and run_id to resultData
|
||||
resultData.eqParam_id = eq_id;
|
||||
resultData.run_id = run_id;
|
||||
|
||||
% Check if exact Equalizer and BER entries already exist in the DB ...
|
||||
selectedFields = {'Runs.run_id','BERs.ber_id','Equalizer.eq_id','BERs.ber',['BERs.occurrence' ...
|
||||
'']};
|
||||
filterParams = obj.tables;
|
||||
filterParams.Equalizer = equalizerData;
|
||||
[dataTable,sql_query] = obj.queryDB(filterParams, selectedFields);
|
||||
% 4. Compute hash for the processing result
|
||||
tempResultData = rmfield(resultData, 'date_of_processing');
|
||||
resultJsonStr = jsonencode(tempResultData);
|
||||
md2 = java.security.MessageDigest.getInstance('MD5'); % Create a new MD5 instance
|
||||
md2.update(uint8(resultJsonStr));
|
||||
resultHashBytes = typecast(md2.digest, 'uint8');
|
||||
resultHashStr = lower(dec2hex(resultHashBytes)');
|
||||
resultHashStr = lower(strtrim(resultHashStr(:)')); % Convert to a lowercase string
|
||||
|
||||
% get or insert Equalizer
|
||||
if ~isempty(dataTable)
|
||||
% Equalizer entry already exists, use the existing eq_id
|
||||
cur_eq_id = dataTable.eq_id;
|
||||
else
|
||||
% Insert the new Equalizer entry
|
||||
cur_eq_id = obj.appendToTable('Equalizer', equalizerData);
|
||||
% Add the result hash to resultData
|
||||
resultData.result_hash = resultHashStr;
|
||||
|
||||
% 5. Check if an identical processing result already exists
|
||||
queryStr2 = sprintf('SELECT result_id FROM Results WHERE result_hash = ''%s''', resultData.result_hash);
|
||||
existingResult = obj.fetch(queryStr2);
|
||||
|
||||
if ~isempty(existingResult)
|
||||
% If the result exists, return its result_id without inserting a new row
|
||||
resultID = existingResult{1,1};
|
||||
warning(['Result already exists: ResultID: ',num2str(resultID),'| EQ ID: ',num2str(eq_id),' Run ID: ' num2str(run_id)])
|
||||
return;
|
||||
end
|
||||
|
||||
% skip if already here or insert BER entry
|
||||
if ~isempty(dataTable)
|
||||
% 6. Insert the processing result
|
||||
resultID = obj.appendToTable('Results', resultData);
|
||||
end
|
||||
|
||||
% A BER entry with the same eq_id, run_id, and occurrence already exists
|
||||
existingBERValue = dataTable.ber;
|
||||
function recalcHashes(obj)
|
||||
% recalcHashes Recalculate hashes for all rows in the Results and EqualizerParameters tables.
|
||||
%
|
||||
% For EqualizerParameters, the hash is computed from all fields except
|
||||
% 'eq_id' and 'config_hash'.
|
||||
%
|
||||
% For Results, the hash is computed from all fields except 'result_id',
|
||||
% 'result_hash', and 'date_of_processing'.
|
||||
|
||||
% Compare the existing BER value with the new BER value
|
||||
if existingBERValue == berValue
|
||||
fprintf('The BER entry %.2e || -- eq_id: %d -- run_id: %d -- occurrence: %d already exists. \n',berValue, cur_eq_id, runID, occurrence);
|
||||
else
|
||||
fprintf('Already found BER for EQ: %.2e ~= %.2e || -- eq_id: %d -- run_id: %d -- occurrence: %d already exists.\n', berValue, existingBERValue, cur_eq_id, runID, occurrence);
|
||||
% Recalculate hashes for EqualizerParameters
|
||||
eqParamsRows = obj.fetch('SELECT * FROM EqualizerParameters');
|
||||
for i = 1:height(eqParamsRows)
|
||||
rowStruct = table2struct(eqParamsRows(i,:)); % Convert the table row to a struct
|
||||
|
||||
% Remove fields not part of the hash computation
|
||||
if isfield(rowStruct, 'eq_id')
|
||||
rowStruct = rmfield(rowStruct, 'eq_id');
|
||||
end
|
||||
if isfield(rowStruct, 'config_hash')
|
||||
rowStruct = rmfield(rowStruct, 'config_hash');
|
||||
end
|
||||
|
||||
else
|
||||
|
||||
% No such BER entry exists, insert the new BER entry
|
||||
berData = struct( ...
|
||||
'run_id', runID, ...
|
||||
'eq_id', cur_eq_id, ...
|
||||
'ber', berValue, ...
|
||||
'occurrence', occurrence ...
|
||||
);
|
||||
|
||||
obj.appendToTable('BERs', berData);
|
||||
% Compute MD5 hash from the JSON representation
|
||||
jsonStr = jsonencode(rowStruct);
|
||||
md = java.security.MessageDigest.getInstance('MD5');
|
||||
md.update(uint8(jsonStr));
|
||||
hashBytes = typecast(md.digest, 'uint8');
|
||||
hashStr = lower(dec2hex(hashBytes)');
|
||||
hashStr = lower(strtrim(hashStr(:)'));
|
||||
|
||||
% Update the config_hash field using the eq_id from the table row
|
||||
eq_id = eqParamsRows.eq_id(i);
|
||||
updateQuery = sprintf('UPDATE EqualizerParameters SET config_hash = ''%s'' WHERE eq_id = %d', hashStr, eq_id);
|
||||
obj.executeSQL(updateQuery);
|
||||
end
|
||||
|
||||
|
||||
% Fetch all rows from the Results table using queryDB with no filters
|
||||
Results = obj.tables.Results;
|
||||
|
||||
if isstruct(Results)
|
||||
newFields = {};
|
||||
fNames = fieldnames(Results);
|
||||
for i = 1:numel(fNames)
|
||||
newFields{end+1} = ['Results.' fNames{i}];
|
||||
end
|
||||
Results = newFields;
|
||||
end
|
||||
[resultsRows, ~] = obj.queryDB(obj.tables, Results);
|
||||
|
||||
for i = 1:height(resultsRows)
|
||||
rowStruct = table2struct(resultsRows(i, :)); % Convert the row to a struct
|
||||
|
||||
% Remove fields not used in the hash calculation
|
||||
if isfield(rowStruct, 'result_id')
|
||||
rowStruct = rmfield(rowStruct, 'result_id');
|
||||
end
|
||||
if isfield(rowStruct, 'result_hash')
|
||||
rowStruct = rmfield(rowStruct, 'result_hash');
|
||||
end
|
||||
if isfield(rowStruct, 'date_of_processing')
|
||||
rowStruct = rmfield(rowStruct, 'date_of_processing');
|
||||
end
|
||||
|
||||
% Compute MD5 hash from the JSON representation
|
||||
jsonStr = jsonencode(rowStruct);
|
||||
md = java.security.MessageDigest.getInstance('MD5');
|
||||
md.update(uint8(jsonStr));
|
||||
hashBytes = typecast(md.digest, 'uint8');
|
||||
hashStr = lower(dec2hex(hashBytes)');
|
||||
hashStr = lower(strtrim(hashStr(:)'));
|
||||
|
||||
% Access the result_id from the table row and update the hash
|
||||
result_id = resultsRows.result_id(i);
|
||||
updateQuery = sprintf('UPDATE Results SET result_hash = ''%s'' WHERE result_id = %d', ...
|
||||
hashStr, result_id);
|
||||
obj.executeSQL(updateQuery);
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function executeSQL(obj, query)
|
||||
% This method executes an SQL statement using MATLAB's execute function.
|
||||
execute(obj.conn, query);
|
||||
end
|
||||
|
||||
|
||||
function answer = fetch(obj,query)
|
||||
answer = fetch(obj.conn,query);
|
||||
end
|
||||
@@ -405,15 +487,46 @@ classdef DBHandler < handle
|
||||
|
||||
function query = constructSQLQuery(obj, filterParams, selectedFields)
|
||||
% constructSQLQuery Constructs the SQL query based on filter parameters and selected fields.
|
||||
%
|
||||
% If selectedFields is provided as a struct, it is converted to a cell array.
|
||||
% The conversion takes the field names and creates entries like:
|
||||
% {'selectedFields.fieldName'} for each field.
|
||||
|
||||
% Construct the SELECT clause dynamically based on user selection
|
||||
% Input check for selectedFields: if it's a struct, convert it to a cell array.
|
||||
if isstruct(selectedFields)
|
||||
newFields = {};
|
||||
tableNames = fieldnames(selectedFields);
|
||||
for t = 1:numel(tableNames)
|
||||
tableStruct = selectedFields.(tableNames{t});
|
||||
fieldNames = fieldnames(tableStruct);
|
||||
for f = 1:numel(fieldNames)
|
||||
if isequal(tableStruct.(fieldNames{f}), 1)
|
||||
newFields{end+1} = sprintf('%s.%s', tableNames{t}, fieldNames{f});
|
||||
end
|
||||
end
|
||||
end
|
||||
selectedFields = newFields;
|
||||
end
|
||||
|
||||
% Construct the SELECT clause dynamically based on user selection.
|
||||
% (Assuming that when provided as a cell array, each entry is of the form
|
||||
% 'TableName.fieldName' or, in our conversion case, 'selectedFields.fieldName'.)
|
||||
selectClause = 'SELECT DISTINCT ';
|
||||
for i = 1:numel(selectedFields)
|
||||
fieldParts = strsplit(selectedFields{i}, '.');
|
||||
tableName = fieldParts{1};
|
||||
fieldName = fieldParts{2};
|
||||
% If the field comes from the struct conversion, its first part is 'selectedFields'
|
||||
% and the actual field name is in the second part.
|
||||
if strcmp(fieldParts{1}, 'selectedFields')
|
||||
tableName = fieldParts{1}; % not used for type checking below
|
||||
fieldName = fieldParts{2};
|
||||
else
|
||||
tableName = fieldParts{1};
|
||||
fieldName = fieldParts{2};
|
||||
end
|
||||
|
||||
if isnumeric(obj.tables.(tableName).(fieldName))
|
||||
% Decide on COALESCE depending on the field type.
|
||||
% If the table is known in obj.tables and the field is numeric, use 'NaN'.
|
||||
if isfield(obj.tables, tableName) && isfield(obj.tables.(tableName), fieldName) && isnumeric(obj.tables.(tableName).(fieldName))
|
||||
selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', ''NaN'') AS ', fieldName];
|
||||
else
|
||||
selectClause = [selectClause, 'COALESCE(', selectedFields{i}, ', '''') AS ', fieldName];
|
||||
@@ -426,235 +539,302 @@ classdef DBHandler < handle
|
||||
end
|
||||
end
|
||||
|
||||
% Construct the FROM and WHERE clause
|
||||
baseQuery = [selectClause, 'FROM Runs ' ...
|
||||
'LEFT JOIN Configurations ON Runs.run_id = Configurations.run_id ' ...
|
||||
'LEFT JOIN Measurements ON Runs.run_id = Measurements.run_id ' ...
|
||||
'WHERE '];
|
||||
% 'LEFT JOIN BERs ON Runs.run_id = BERs.run_id ' ...
|
||||
% 'LEFT JOIN Equalizer ON BERs.eq_id = Equalizer.eq_id ' ...
|
||||
|
||||
% --- Adaptive FROM Clause ---
|
||||
% Use "Runs" as the main table and add LEFT JOINs for every other table in obj.tables
|
||||
% (except "sqlite_sequence") that has a run_id field.
|
||||
mainTable = 'Runs';
|
||||
fromClause = ['FROM ', mainTable, ' '];
|
||||
tableNamesAll = fieldnames(obj.tables);
|
||||
for t = 1:numel(tableNamesAll)
|
||||
tableName = tableNamesAll{t};
|
||||
if strcmpi(tableName, mainTable) || strcmpi(tableName, 'sqlite_sequence')
|
||||
continue;
|
||||
end
|
||||
|
||||
% Loop through each table in filterParams
|
||||
if isfield(obj.tables.(tableName), 'run_id')
|
||||
% most tables are directly linked to runs table
|
||||
fromClause = [fromClause, 'LEFT JOIN ', tableName, ' ON ', mainTable, '.run_id = ', tableName, '.run_id '];
|
||||
elseif isfield(obj.tables.(tableName), 'eq_id')
|
||||
% equalizer is only linked to results table
|
||||
fromClause = [fromClause, 'LEFT JOIN ', tableName, ' ON ', 'Results', '.eqParam_id = ', tableName, '.eq_id '];
|
||||
end
|
||||
end
|
||||
|
||||
% --- WHERE Clause Construction ---
|
||||
baseQuery = [selectClause, ' ', fromClause, 'WHERE '];
|
||||
filterClauses = [];
|
||||
tableNames_ = fieldnames(filterParams);
|
||||
|
||||
for t = 1:numel(tableNames_)
|
||||
tableName = tableNames_{t};
|
||||
tableParams = filterParams.(tableName);
|
||||
|
||||
% Loop through each parameter in the table
|
||||
fieldNames = fieldnames(tableParams);
|
||||
for i = 1:numel(fieldNames)
|
||||
fieldName = fieldNames{i};
|
||||
value = tableParams.(fieldName);
|
||||
|
||||
% Construct the full column name in the format "tableName.fieldName"
|
||||
fullName = sprintf('%s.%s', tableName, fieldName);
|
||||
|
||||
% Handle different types of values for SQL query construction
|
||||
% Handle various types of values for SQL query construction
|
||||
if isempty(value)
|
||||
% Skip this parameter if it is empty (include all values)
|
||||
continue;
|
||||
elseif isnumeric(value) && isnan(value)
|
||||
% If value is NaN, use IS NULL in SQL
|
||||
filterClause = sprintf('%s IS NULL', fullName);
|
||||
elseif isnumeric(value) && ~isEnumeration(value)
|
||||
filterClause = sprintf('%s = %f', fullName, value);
|
||||
elseif islogical(value) || (isnumeric(value) && ismember(value, [0, 1])) && ~isEnumeration(value)
|
||||
filterClause = sprintf('%s = %d', fullName, value);
|
||||
elseif ischar(value) || isstring(value)
|
||||
filterClause = sprintf('%s = ''%s''', fullName, char(value)); %nicht nach string suchen sondern nach chararray -> 'bla' statt "bla"
|
||||
filterClause = sprintf('%s = ''%s''', fullName, char(value));
|
||||
elseif isEnumeration(value)
|
||||
filterClause = sprintf('%s = ''%s''', fullName, value);
|
||||
else
|
||||
error('Unsupported data type for field "%s".', fullName);
|
||||
end
|
||||
|
||||
% Add the constructed filter clause to the list
|
||||
filterClauses = [filterClauses, filterClause, ' AND '];
|
||||
end
|
||||
end
|
||||
|
||||
% Remove trailing ' AND ' from the filter clauses if any filters were added
|
||||
% Remove trailing ' AND ' if any filters were added.
|
||||
if ~isempty(filterClauses)
|
||||
filterClauses = filterClauses(1:end-5);
|
||||
end
|
||||
|
||||
% Construct the final SQL query
|
||||
if isempty(filterClauses)
|
||||
query = [selectClause, 'FROM Runs ' ...
|
||||
'LEFT JOIN Configurations ON Runs.run_id = Configurations.run_id ' ...
|
||||
'LEFT JOIN Measurements ON Runs.run_id = Measurements.run_id ' ...
|
||||
'LEFT JOIN BERs ON Runs.run_id = BERs.run_id'];
|
||||
query = [selectClause, ' ', fromClause, 'WHERE ', filterClauses];
|
||||
else
|
||||
query = [baseQuery, filterClauses];
|
||||
query = [selectClause, ' ', fromClause];
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
function selectedFields = promptSelectFields(obj)
|
||||
% promptSelectFields Prompts the user to select fields from multiple tables to include in the SELECT statement using settingsdlg.
|
||||
% promptSelectFields Prompts the user to select fields from multiple tables
|
||||
% using a custom checkbox GUI with scrolling.
|
||||
%
|
||||
% The function builds a list of all fields (formatted as 'TableName.fieldName')
|
||||
% and displays each as a checkbox inside an inner container panel. The container's
|
||||
% height is set to accommodate all checkboxes, so the scrollable panel shows scrollbars.
|
||||
% When the user clicks the "Select" button, the selected fields are returned.
|
||||
% If none are selected, all fields are returned.
|
||||
|
||||
% Get all possible fields from all tables (excluding sqlite_sequence)
|
||||
% Get all possible tables (excluding sqlite_sequence)
|
||||
tableNames = fieldnames(obj.tables);
|
||||
tableNames = setdiff(tableNames, {'sqlite_sequence'}); % Remove sqlite_sequence
|
||||
|
||||
% Prepare the inputs for settingsdlg
|
||||
promptSettings = {};
|
||||
allFieldsFullName = {};
|
||||
convertedFieldNames = {};
|
||||
tableNames = setdiff(tableNames, {'sqlite_sequence'});
|
||||
|
||||
% Build a single cell array of all field names with table prefix.
|
||||
allFields = {};
|
||||
for i = 1:numel(tableNames)
|
||||
tableFields = fieldnames(obj.tables.(tableNames{i}));
|
||||
for j = 1:numel(tableFields)
|
||||
fieldName = tableFields{j};
|
||||
fullName = sprintf('%s.%s', tableNames{i}, fieldName);
|
||||
convertedName = strrep(fullName, '.', '_'); % Replace '.' with '_'
|
||||
|
||||
allFieldsFullName{end + 1} = fullName; % Add full name to the list
|
||||
convertedFieldNames{end + 1} = convertedName; % Store the converted name
|
||||
|
||||
% Add the field name and checkbox setting to the prompt
|
||||
promptSettings{end + 1} = {sprintf('Include %s', fullName), convertedName};
|
||||
promptSettings{end + 1} = false; % Default: not selected
|
||||
fields = fieldnames(obj.tables.(tableNames{i}));
|
||||
for j = 1:numel(fields)
|
||||
allFields{end+1} = sprintf('%s.%s', tableNames{i}, fields{j});
|
||||
end
|
||||
end
|
||||
numFields = numel(allFields);
|
||||
|
||||
% Create the settings dialog
|
||||
[settings, button] = settingsdlg(...
|
||||
'title', 'Select Fields for the SQL Query', ...
|
||||
'description', 'Check the boxes for the fields you want to include in the SELECT statement.', ...
|
||||
promptSettings{:} ...
|
||||
);
|
||||
% Create the main figure.
|
||||
fig = uifigure('Name', 'Select Fields', 'Position', [100, 100, 400, 600]);
|
||||
|
||||
% If the user cancels, default to selecting all fields
|
||||
if strcmp(button, 'cancel')
|
||||
selectedFields = allFieldsFullName;
|
||||
return;
|
||||
% Create a scrollable panel.
|
||||
scrollPanel = uipanel(fig, 'Position', [10, 60, 380, 530], 'Scrollable', 'on');
|
||||
|
||||
% Define checkbox dimensions.
|
||||
checkboxHeight = 30;
|
||||
spacing = 5;
|
||||
totalHeight = numFields * (checkboxHeight + spacing);
|
||||
|
||||
% Create an inner container panel with height larger than the scrollPanel's height.
|
||||
container = uipanel(scrollPanel, 'Position', [0, 0, scrollPanel.Position(3), totalHeight]);
|
||||
|
||||
% Create checkboxes using absolute positioning in the container.
|
||||
checkboxes = gobjects(numFields, 1);
|
||||
for i = 1:numFields
|
||||
% Calculate the vertical position.
|
||||
% The origin (0,0) is at the bottom left of the container.
|
||||
yPos = totalHeight - i*(checkboxHeight + spacing) + spacing;
|
||||
checkboxes(i) = uicheckbox(container, ...
|
||||
'Text', allFields{i}, ...
|
||||
'Value', false, ...
|
||||
'Position', [10, yPos, container.Position(3)-20, checkboxHeight]);
|
||||
end
|
||||
|
||||
% Parse user input into selectedFields
|
||||
% Create a "Select" button in the main figure.
|
||||
btn = uibutton(fig, 'Text', 'Select', ...
|
||||
'Position', [150, 10, 100, 30], ...
|
||||
'ButtonPushedFcn', @(btn, event) uiresume(fig));
|
||||
|
||||
% Wait for the user to click the button.
|
||||
uiwait(fig);
|
||||
|
||||
% Retrieve the selected fields.
|
||||
selectedFields = {};
|
||||
for i = 1:numel(allFieldsFullName)
|
||||
convertedName = convertedFieldNames{i};
|
||||
if isfield(settings, convertedName) && settings.(convertedName) % Add to selectedFields if the checkbox was selected
|
||||
selectedFields{end + 1} = allFieldsFullName{i}; %#ok<AGROW>
|
||||
for i = 1:numFields
|
||||
if checkboxes(i).Value
|
||||
selectedFields{end+1} = checkboxes(i).Text;
|
||||
end
|
||||
end
|
||||
|
||||
% If no fields are selected, default to selecting all fields
|
||||
% If no fields are selected, default to all fields.
|
||||
if isempty(selectedFields)
|
||||
selectedFields = allFieldsFullName;
|
||||
selectedFields = allFields;
|
||||
end
|
||||
|
||||
% Close the figure.
|
||||
delete(fig);
|
||||
end
|
||||
|
||||
|
||||
|
||||
function filterParams = promptFilterParameters(obj)
|
||||
% promptFilterParameters Prompts the user to enter filter parameters using the settingsdlg framework.
|
||||
% promptFilterParameters Prompts the user to enter filter parameters using a
|
||||
% custom scrollable UI with dropdowns.
|
||||
%
|
||||
% For each table (excluding 'sqlite_sequence'), each field that has distinct
|
||||
% values is displayed as a label and a dropdown. The dropdown items are built
|
||||
% from the distinct values (with "All" prepended). The output is a struct where,
|
||||
% for each table, each field is set to the chosen value (or [] if "All" is selected).
|
||||
|
||||
% Get all possible parameters from all tables (excluding sqlite_sequence)
|
||||
tableNames_ = fieldnames(obj.tables);
|
||||
tableNames_ = setdiff(tableNames_, {'sqlite_sequence'}); % Remove sqlite_sequence
|
||||
% Get all tables except 'sqlite_sequence'
|
||||
tableNames = fieldnames(obj.tables);
|
||||
tableNames = setdiff(tableNames, {'sqlite_sequence'});
|
||||
|
||||
% Prepare the inputs for settingsdlg with sections and separators
|
||||
promptSettings = {};
|
||||
allFieldsFullName = {};
|
||||
convertedFieldNames = {};
|
||||
% Precompute layout constants.
|
||||
heightPerTableLabel = 30;
|
||||
heightPerField = 40; % vertical space for a field (label + dropdown)
|
||||
spacing = 5;
|
||||
|
||||
for i = 1:numel(tableNames_)
|
||||
% Add a separator for each table section
|
||||
promptSettings{end + 1} = 'separator';
|
||||
promptSettings{end + 1} = tableNames_{i};
|
||||
|
||||
% Get all fields from the current table
|
||||
tableFields = fieldnames(obj.tables.(tableNames_{i}));
|
||||
|
||||
% Prepare each field to be added to the dialog
|
||||
% Compute total required height.
|
||||
totalHeight = 0;
|
||||
for i = 1:numel(tableNames)
|
||||
totalHeight = totalHeight + heightPerTableLabel;
|
||||
tableName = tableNames{i};
|
||||
tableFields = fieldnames(obj.tables.(tableName));
|
||||
for j = 1:numel(tableFields)
|
||||
fieldName = tableFields{j};
|
||||
fullName = sprintf('%s.%s', tableNames_{i}, fieldName);
|
||||
convertedName = strrep(fullName, '.', '_'); % Replace '.' with '_'
|
||||
|
||||
% Skip fields that do not have distinct values stored
|
||||
if ~isfield(obj.distinctValues.(tableNames_{i}), fieldName)
|
||||
continue;
|
||||
% Only include fields that have distinct values stored.
|
||||
if isfield(obj.distinctValues.(tableName), fieldName)
|
||||
totalHeight = totalHeight + heightPerField;
|
||||
end
|
||||
|
||||
% Get the distinct values for the field
|
||||
distinctValues_ = obj.distinctValues.(tableNames_{i}).(fieldName);
|
||||
|
||||
% Prepare distinct values for dropdown
|
||||
if isempty(distinctValues_)
|
||||
% If there are no distinct values, use only an "All" entry
|
||||
distinctValues_ = {'All'};
|
||||
else
|
||||
% Ensure distinctValues is a cell array of strings
|
||||
if isnumeric(distinctValues_)
|
||||
distinctValues_ = arrayfun(@(x) num2str(x), distinctValues_, 'UniformOutput', false);
|
||||
elseif isstring(distinctValues_)
|
||||
distinctValues_ = cellstr(distinctValues_);
|
||||
elseif iscell(distinctValues_) && ~iscellstr(distinctValues_)
|
||||
distinctValues_ = cellfun(@num2str, distinctValues_, 'UniformOutput', false);
|
||||
end
|
||||
|
||||
% Add an "All" option at the beginning of the distinct values list
|
||||
distinctValues_ = [{'All'}; distinctValues_];
|
||||
end
|
||||
|
||||
allFieldsFullName{end + 1} = fullName; % Add full name to the list
|
||||
convertedFieldNames{end + 1} = convertedName; % Store the converted name
|
||||
|
||||
% Add the field name and value setting to the prompt
|
||||
promptSettings{end + 1} = {sprintf('%s', fullName), convertedName};
|
||||
promptSettings{end + 1} = distinctValues_; % Add distinct values as dropdown options
|
||||
end
|
||||
end
|
||||
|
||||
% Create the settings dialog
|
||||
[settings, button] = settingsdlg(...
|
||||
'title', 'Input Parameters for Filtering', ...
|
||||
'description', 'Enter the values for each field to filter. Select "All" to include all values.', ...
|
||||
promptSettings{:} ...
|
||||
);
|
||||
% Create the main UI figure.
|
||||
fig = uifigure('Name', 'Input Parameters for Filtering', 'Position', [100, 100, 500, 600]);
|
||||
|
||||
% If the user cancels, return an empty struct
|
||||
if strcmp(button, 'cancel')
|
||||
% Set a CloseRequestFcn so that closing the figure calls uiresume.
|
||||
fig.CloseRequestFcn = @(src, event) uiresume(src);
|
||||
|
||||
% Create a scrollable panel inside the figure.
|
||||
scrollPanel = uipanel(fig, 'Position', [10, 60, 480, 530], 'Scrollable', 'on');
|
||||
|
||||
% Create an inner container panel with a height set to totalHeight.
|
||||
container = uipanel(scrollPanel, 'Position', [0, 0, scrollPanel.Position(3), totalHeight]);
|
||||
|
||||
% Prepare cell arrays to store dropdown handles and corresponding table/field names.
|
||||
dropdownHandles = {};
|
||||
dropdownTableNames = {};
|
||||
dropdownFieldNames = {};
|
||||
|
||||
% Set the starting Y coordinate (filling from top to bottom).
|
||||
currentY = totalHeight;
|
||||
|
||||
% Maximum number of dropdown items.
|
||||
maxItems = 100;
|
||||
|
||||
for i = 1:numel(tableNames)
|
||||
% Create a label for the table name.
|
||||
uilabel(container, ...
|
||||
'Text', tableNames{i}, ...
|
||||
'FontWeight', 'bold', ...
|
||||
'Position', [10, currentY - heightPerTableLabel + spacing, 200, heightPerTableLabel - spacing]);
|
||||
currentY = currentY - heightPerTableLabel;
|
||||
|
||||
tableName = tableNames{i};
|
||||
tableFields = fieldnames(obj.tables.(tableName));
|
||||
for j = 1:numel(tableFields)
|
||||
fieldName = tableFields{j};
|
||||
if ~isfield(obj.distinctValues.(tableName), fieldName)
|
||||
continue; % Skip if no distinct values are stored.
|
||||
end
|
||||
|
||||
% Retrieve distinct values for the field.
|
||||
distinctValues_ = obj.distinctValues.(tableName).(fieldName);
|
||||
if ~isempty(distinctValues_) && numel(distinctValues_) > maxItems
|
||||
distinctValues_ = distinctValues_(1:maxItems);
|
||||
end
|
||||
|
||||
if isempty(distinctValues_)
|
||||
items = {'All'};
|
||||
else
|
||||
if isnumeric(distinctValues_)
|
||||
items = cellfun(@num2str, num2cell(distinctValues_), 'UniformOutput', false);
|
||||
elseif isstring(distinctValues_)
|
||||
items = cellstr(distinctValues_);
|
||||
elseif iscell(distinctValues_) && ~iscellstr(distinctValues_)
|
||||
items = cellfun(@num2str, distinctValues_, 'UniformOutput', false);
|
||||
else
|
||||
items = distinctValues_;
|
||||
end
|
||||
items = items(:)'; % Ensure row vector
|
||||
items = [{'All'}, items];
|
||||
end
|
||||
|
||||
% Create a label for the field.
|
||||
uilabel(container, ...
|
||||
'Text', sprintf('%s:', fieldName), ...
|
||||
'HorizontalAlignment', 'right', ...
|
||||
'Position', [10, currentY - 25, 150, 25]);
|
||||
|
||||
% Create a dropdown for the field.
|
||||
dd = uidropdown(container, ...
|
||||
'Items', items, ...
|
||||
'Value', 'All', ...
|
||||
'Position', [170, currentY - 25, 200, 25]);
|
||||
|
||||
% Store the dropdown handle and its associated table/field.
|
||||
dropdownHandles{end+1} = dd;
|
||||
dropdownTableNames{end+1} = tableName;
|
||||
dropdownFieldNames{end+1} = fieldName;
|
||||
|
||||
currentY = currentY - heightPerField;
|
||||
end
|
||||
end
|
||||
|
||||
% Create a "Submit" button at the bottom of the figure.
|
||||
btn = uibutton(fig, 'Text', 'Submit', ...
|
||||
'Position', [200, 10, 100, 30], ...
|
||||
'ButtonPushedFcn', @(btn, event) uiresume(fig));
|
||||
|
||||
% Wait until the user clicks "Submit" or closes the figure.
|
||||
uiwait(fig);
|
||||
|
||||
% If the figure was closed (and thus no longer valid), return an empty struct.
|
||||
if ~isvalid(fig)
|
||||
filterParams = struct();
|
||||
return;
|
||||
end
|
||||
|
||||
% Parse user input into filterParams structure
|
||||
% Build the filterParams struct from the dropdown selections.
|
||||
filterParams = struct();
|
||||
|
||||
for i = 1:numel(allFieldsFullName)
|
||||
value = settings.(convertedFieldNames{i});
|
||||
|
||||
% Split full name to get table and field names
|
||||
fieldParts = strsplit(allFieldsFullName{i}, '.');
|
||||
tableName = fieldParts{1};
|
||||
fieldName = fieldParts{2};
|
||||
|
||||
% If the table does not exist in the filterParams struct, create it
|
||||
for k = 1:numel(dropdownHandles)
|
||||
tableName = dropdownTableNames{k};
|
||||
fieldName = dropdownFieldNames{k};
|
||||
value = dropdownHandles{k}.Value;
|
||||
if ~isfield(filterParams, tableName)
|
||||
filterParams.(tableName) = struct();
|
||||
end
|
||||
|
||||
% Assign values to the respective fields under each table
|
||||
% If "All" is selected, assign empty; otherwise, try converting to numeric.
|
||||
if strcmp(value, 'All')
|
||||
filterParams.(tableName).(fieldName) = []; % Set to empty to include all values
|
||||
elseif isnumeric(value) && isnan(value)
|
||||
filterParams.(tableName).(fieldName) = NaN; % Use NaN to handle as NULL
|
||||
filterParams.(tableName).(fieldName) = [];
|
||||
else
|
||||
filterParams.(tableName).(fieldName) = value; % Use the entered value
|
||||
numValue = str2double(value);
|
||||
if ~isnan(numValue)
|
||||
filterParams.(tableName).(fieldName) = numValue;
|
||||
else
|
||||
filterParams.(tableName).(fieldName) = value;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
% Close the figure.
|
||||
delete(fig);
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user