From 7b70030969c0601fbec83adce14dbdcfa10f4cca Mon Sep 17 00:00:00 2001 From: Javinator9889 Date: Mon, 4 Nov 2019 14:38:35 +0100 Subject: [PATCH] Finished database model --- Design/Database/exporter.py | 257 +++++++++++++++++ Design/Database/generated_sql_file.sql | 18 +- .../Database/generated_sql_file_postgre.sql | 235 ++++++++++++++++ Design/Database/new_database_model.mwb | Bin 0 -> 11153 bytes Design/Database/new_database_model.mwb.bak | Bin 0 -> 11494 bytes Design/Database/psql_model.sql | 266 ++++++++++++++++++ YouTubeMDBot/.idea/.gitignore | 3 + YouTubeMDBot/.idea/YouTubeMDBot.iml | 11 + .../.idea/codeStyles/codeStyleConfig.xml | 5 + .../.idea/dictionaries/javinator9889.xml | 10 + YouTubeMDBot/.idea/git_toolbox_prj.xml | 6 + .../inspectionProfiles/profiles_settings.xml | 6 + YouTubeMDBot/.idea/misc.xml | 7 + YouTubeMDBot/.idea/modules.xml | 8 + YouTubeMDBot/.idea/vcs.xml | 6 + 15 files changed, 829 insertions(+), 9 deletions(-) create mode 100755 Design/Database/exporter.py create mode 100644 Design/Database/generated_sql_file_postgre.sql create mode 100644 Design/Database/new_database_model.mwb create mode 100644 Design/Database/new_database_model.mwb.bak create mode 100644 Design/Database/psql_model.sql create mode 100755 YouTubeMDBot/.idea/.gitignore create mode 100755 YouTubeMDBot/.idea/YouTubeMDBot.iml create mode 100755 YouTubeMDBot/.idea/codeStyles/codeStyleConfig.xml create mode 100755 YouTubeMDBot/.idea/dictionaries/javinator9889.xml create mode 100644 YouTubeMDBot/.idea/git_toolbox_prj.xml create mode 100755 YouTubeMDBot/.idea/inspectionProfiles/profiles_settings.xml create mode 100755 YouTubeMDBot/.idea/misc.xml create mode 100755 YouTubeMDBot/.idea/modules.xml create mode 100755 YouTubeMDBot/.idea/vcs.xml diff --git a/Design/Database/exporter.py b/Design/Database/exporter.py new file mode 100755 index 0000000..937d141 --- /dev/null +++ b/Design/Database/exporter.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python +# +# -*- coding: utf-8 -*- +# +# Copyright (C) 2011 by Aevum Softwares LTDA ME +# +# This is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys +import optparse +import os +import re + +__version__ = 0.1 +SEQUENCE_SUFFIX = "sequence" +SCHEMA = ""; + +def remove_comments(string): + string = re.sub(re.compile("/\*.*?\*/", re.DOTALL), "", string) # remove all block comments (/*COMMENT */) + string = re.sub(re.compile("//.*?\n"), "", string) # remove all single line comments (//COMMENT\n) + return string + + +def remove_lines_started_with(word, lines): + toRemove = [] + for line in lines: + if line.startswith(word): + toRemove.append(line) + for line in toRemove: + lines.remove(line) + + +def remove_lines_with(word, lines): + toRemove = [] + for line in lines: + if word in line: + toRemove.append(line) + for line in toRemove: + lines.remove(line) + + +def remove_word(word, lines, numberOfNextWords=0): + substitute = "@@@" + for i in range(len(lines)): + if not numberOfNextWords: + lines[i] = lines[i].replace(word, "") + else: + line = lines[i] + line = line.replace(word, substitute) + if substitute in line: + split = line.split() + nextWords = [] + for j in range(len(split)): + if split[j] == substitute: + for k in range(numberOfNextWords): + nextWords.append(split[k + j + 1]) + replaceString = substitute + for k in range(numberOfNextWords): + replaceString = "{0} {1}".format(replaceString, nextWords[k]) + lines[i] = line.replace(replaceString, "") + + +def put_semicolons(lines): + numberOfOpenningParenthesis = 0 + numberOfClosingParenthesis = 0 + for i in range(len(lines)): + line = lines[i] + split = lines[i].split() + if "CREATE" in split and "TABLE" in split: + if split.index("CREATE") == split.index("TABLE") - 1: + numberOfOpenningParenthesis = 0 + numberOfClosingParenthesis = 0 + + while "(" in line: + line = line.replace("(", "", 1) + numberOfOpenningParenthesis = numberOfOpenningParenthesis + 1 + + while ")" in line: + line = line.replace(")", "", 1) + numberOfClosingParenthesis = numberOfClosingParenthesis + 1 + + if numberOfOpenningParenthesis == numberOfClosingParenthesis: + if numberOfOpenningParenthesis != 0: + lines[i] = lines[i].replace("\n", "") + ";\n" + numberOfOpenningParenthesis = 0 + numberOfClosingParenthesis = 0 + + +def create_sequences(lines): + sequences = [] + lastTable = None + for i in range(len(lines)): + line = lines[i] + split = line.split() + if "CREATE" in split and "TABLE" in split: + if split.index("CREATE") == split.index("TABLE") - 1: + lastTable = split[split.index("CREATE") + 2] + elif "AUTO_INCREMENT" in line: + if "." in lastTable: + spl = lastTable.replace("\"", "").split(".") + schema = "\"" + spl[0] + "\"." + table = spl[1] + else: + table = lastTable.replace("\"", "") + schema = "" + + column = split[0].replace("\"", "") + sequences.append({"table": table, "column": column, "schema": schema}) + + for sequence in sequences: + lines.append("{0} {1}\"{2}_{3}_{4}\";\n".format( + "DROP SEQUENCE IF EXISTS", + sequence["schema"], + sequence["table"], + sequence["column"], + SEQUENCE_SUFFIX)) + lines.append("{0} {1}\"{2}_{3}_{4}\";\n".format( + "CREATE SEQUENCE ", + sequence["schema"], + sequence["table"], + sequence["column"], + SEQUENCE_SUFFIX)) + lines.append("ALTER TABLE {0}\"{1}\" ALTER COLUMN \"{2}\" SET DEFAULT NEXTVAL('{0}\"{1}_{2}_{3}\"');\n".format( + sequence["schema"], + sequence["table"], + sequence["column"], + SEQUENCE_SUFFIX,)) +def get_current_schema(lines): + for line in lines: + if "CREATE SCHEMA" in line: + return re.sub('\s+',' ',line.replace(';', '').replace('"', '')).strip().split(' ')[-1] + elif "CREATE TABLE" in line: + tbl = re.search("CREATE TABLE (.*) \(", line).group(1).replace('"', '') + if "." in tbl: + return tbl.split('.')[0].strip() + return None + +def set_schema(lines): + currSchema = get_current_schema(lines) + if currSchema == None: + print("There is no schema set on the original script! Will default to public schema") # Not implemented: add schema to all statements + else: + for i in range(len(lines)): + line = lines[i] + regex = re.compile("((" + currSchema + ")(\")?(\.|;))") + search = regex.search(line) + if search: + sufix = '' + if search.group(3): + sufix += search.group(3); + if search.group(4): + sufix += search.group(4) + + line = re.sub(regex, SCHEMA + sufix, line) + lines[i] = line + +def replace_word(word, replace, lines): + for i in range(len(lines)): + lines[i] = lines[i].replace(word, replace) + + +def replace_regex(search, replace, lines): + for i in range(len(lines)): + lines[i] = re.sub(re.compile(search), replace, lines[i]) + +def add_cascade_to_drops(lines): + for i in range(len(lines)): + line = lines[i] + if line.startswith("DROP"): + lines[i] = line.replace(";", "CASCADE;") + + +def convert(input, output): + contents = input.read() + lines = remove_comments(contents).splitlines(True) + remove_lines_with("ASC)", lines) + remove_lines_started_with("SET", lines) + remove_lines_started_with("COLLATE", lines) + remove_lines_started_with("ENGINE", lines) + remove_lines_started_with("COMMENT", lines) + remove_lines_started_with("PACK_KEYS", lines) + replace_regex("COMMENT .*,\n", ",\n", lines) + remove_word(" COMMENT ", lines) + remove_lines_started_with("USE", lines) + replace_word("`", "\"", lines) + #remove_word("'", lines) # this breaks DEFAULT '' statements + remove_word("UNSIGNED", lines) + remove_word("IF NOT EXISTS", lines) + replace_regex("CREATE SCHEMA (.*);", r"CREATE SCHEMA IF NOT EXISTS \1;", lines) + remove_word("DEFAULT CHARACTER SET =", lines, 1) + remove_word("DEFAULT CHARACTER SET", lines, 1) + remove_word("CHARACTER SET", lines, 1) + remove_word("COLLATE", lines, 1) + replace_word("DATETIME", "TIMESTAMP", lines) + replace_word("TINYINT(1)", "BOOLEAN", lines) + replace_word("LONGTEXT", "TEXT", lines) + replace_regex("INT\(\d*\)", "INT", lines) + remove_word("CONSTRAINT \"\"", lines) + remove_word("FOREIGN KEY ()", lines) + put_semicolons(lines) + add_cascade_to_drops(lines) + create_sequences(lines) + remove_word("AUTO_INCREMENT", lines) + if SCHEMA != "": + set_schema(lines); + + output.writelines(lines) + +def main(args): + """Check arguments from the command line and executed the required action""" + parser = optparse.OptionParser( + usage="Usage: %prog [options] []", + version="%prog {0}".format(__version__)) + parser.add_option("-s", "--schema", + action="store", dest="schema", + help="Schema name for the Postgre script. Default value is the table name for MySQL Workbench."); + # parser.add_option("-o", "--output", + # action="store", dest="output", + # help="Generates the output") + (options, args) = parser.parse_args() + if len(args) > 0: + input_path = args[0] + if not os.path.exists(input_path): + print("First argument should be a valid sql file") + return + + if len(args) < 2: + output_path = "{0}_postgre.{1}".format(*input_path.rsplit('.', 1)) + else: + output_path = args[1] + + if options.schema != "" and options.schema != None: + global SCHEMA + SCHEMA = options.schema + + input = open(input_path, "r") + output = open(output_path, "w") + convert(input, output) + else: + print ("Invalid parameters. You should run YYY []") + return + + +if __name__ == "__main__": + main(sys.argv) + diff --git a/Design/Database/generated_sql_file.sql b/Design/Database/generated_sql_file.sql index 7ced06a..ff02b5e 100755 --- a/Design/Database/generated_sql_file.sql +++ b/Design/Database/generated_sql_file.sql @@ -3,9 +3,9 @@ -- Model: New Model Version: 1.0 -- MySQL Workbench Forward Engineering -SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; -SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; -SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; +-- SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +-- SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +-- SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; -- ----------------------------------------------------- -- Schema youtubemd @@ -16,7 +16,7 @@ SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,N -- ----------------------------------------------------- CREATE SCHEMA IF NOT EXISTS `youtubemd` DEFAULT CHARACTER SET utf8mb4 ; SHOW WARNINGS; -USE `youtubemd` ; +-- USE `youtubemd` ; -- ----------------------------------------------------- -- Table `youtubemd`.`DownloadInformation` @@ -245,8 +245,8 @@ CREATE TABLE IF NOT EXISTS `youtubemd`.`VideoStatistics` ( ENGINE = InnoDB CHECKSUM = 1; -SHOW WARNINGS; - -SET SQL_MODE=@OLD_SQL_MODE; -SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; -SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; +-- SHOW WARNINGS; +-- +-- SET SQL_MODE=@OLD_SQL_MODE; +-- SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +-- SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/Design/Database/generated_sql_file_postgre.sql b/Design/Database/generated_sql_file_postgre.sql new file mode 100644 index 0000000..15c22f4 --- /dev/null +++ b/Design/Database/generated_sql_file_postgre.sql @@ -0,0 +1,235 @@ +-- MySQL Script generated by MySQL Workbench +-- jue 25 jul 2019 13:50:06 CEST +-- Model: New Model Version: 1.0 +-- MySQL Workbench Forward Engineering + +-- SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +-- SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +-- SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + +-- ----------------------------------------------------- +-- Schema youtubemd +-- ----------------------------------------------------- + +-- ----------------------------------------------------- +-- Schema youtubemd +-- ----------------------------------------------------- +CREATE SCHEMA IF NOT EXISTS "youtubemd" ; +-- SHOW WARNINGS; +-- USE "youtubemd" ; +-- +-- ----------------------------------------------------- +-- Table "youtubemd"."DownloadInformation" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."DownloadInformation" ( + "file_id" VARCHAR(50) NOT NULL, + "audioQuality" ENUM('320k', '256k', '128k') NOT NULL, + "audioSampling" ENUM('44000', '48000') NULL, + "Metadata_idMetadata" INT NOT NULL, + "VideoInformation_id" VARCHAR(11) NOT NULL, + PRIMARY KEY ("file_id", "Metadata_idMetadata", "VideoInformation_id"), + CONSTRAINT "fk_DownloadInformation_Metadata1" + FOREIGN KEY ("Metadata_idMetadata") + REFERENCES "youtubemd"."Metadata" ("idMetadata") + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT "fk_DownloadInformation_VideoInformation1" + FOREIGN KEY ("VideoInformation_id") + REFERENCES "youtubemd"."VideoInformation" ("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION); +CHECKSUM = 1; + +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."DownloadStatistics" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."DownloadStatistics" ( + "timesRequested" INT NOT NULL DEFAULT 0, + "DownloadInformation_file_id" VARCHAR(50) NOT NULL, + PRIMARY KEY ("DownloadInformation_file_id"), + CONSTRAINT "fk_DownloadStatistics_DownloadInformation1" + FOREIGN KEY ("DownloadInformation_file_id") + REFERENCES "youtubemd"."DownloadInformation" ("file_id") + ON DELETE NO ACTION + ON UPDATE NO ACTION); +CHECKSUM = 1; + +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."History" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."History" ( + "User_id" INT NOT NULL, + "DownloadInformation_file_id" VARCHAR(50) NOT NULL, + PRIMARY KEY ("User_id", "DownloadInformation_file_id"), + CONSTRAINT "fk_History_User1" + FOREIGN KEY ("User_id") + REFERENCES "youtubemd"."User" ("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT "fk_History_DownloadInformation1" + FOREIGN KEY ("DownloadInformation_file_id") + REFERENCES "youtubemd"."DownloadInformation" ("file_id") + ON DELETE NO ACTION + ON UPDATE NO ACTION); +CHECKSUM = 1; + +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."Metadata" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."Metadata" ( + "idMetadata" INT NOT NULL , + "title" VARCHAR(100) NOT NULL, + "artist" VARCHAR(60) NOT NULL, + "cover" BLOB NOT NULL, + "duration" INT NULL, + "customMetadata" TINYINT NOT NULL DEFAULT 0, + PRIMARY KEY ("idMetadata")); +CHECKSUM = 1; + +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."Playlist" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."Playlist" ( + "id" VARCHAR(60) NOT NULL, + PRIMARY KEY ("id")); +CHECKSUM = 1; + +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."Playlist_has_VideoInformation" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."Playlist_has_VideoInformation" ( + "Playlist_id" VARCHAR(60) NOT NULL, + "VideoInformation_id" VARCHAR(11) NOT NULL, + PRIMARY KEY ("Playlist_id", "VideoInformation_id"), + CONSTRAINT "fk_Playlist_has_VideoInformation_Playlist1" + FOREIGN KEY ("Playlist_id") + REFERENCES "youtubemd"."Playlist" ("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION, + CONSTRAINT "fk_Playlist_has_VideoInformation_VideoInformation1" + FOREIGN KEY ("VideoInformation_id") + REFERENCES "youtubemd"."VideoInformation" ("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION); +CHECKSUM = 1; + +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."PlaylistStatistics" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."PlaylistStatistics" ( + "timesRequested" INT NOT NULL, + "Playlist_id" VARCHAR(60) NOT NULL, + PRIMARY KEY ("Playlist_id"), + CONSTRAINT "fk_PlaylistStatistics_Playlist1" + FOREIGN KEY ("Playlist_id") + REFERENCES "youtubemd"."Playlist" ("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION); +CHECKSUM = 1; + +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."Preferences" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."Preferences" ( + "language" VARCHAR(3) NOT NULL DEFAULT 'en', + "audioQuality" ENUM('320k', '256k', '128k') NOT NULL DEFAULT '128k', + "audioSampling" ENUM('44000', '48000') NOT NULL DEFAULT '44000', + "sendSongLinks" TINYINT NOT NULL DEFAULT 0, + "askForMetadata" TINYINT NOT NULL DEFAULT 1, + "User_id" INT NOT NULL, + PRIMARY KEY ("User_id"), + CONSTRAINT "fk_Preferences_User" + FOREIGN KEY ("User_id") + REFERENCES "youtubemd"."User" ("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION); +CHECKSUM = 1; + +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."User" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."User" ( + "id" INT NOT NULL DEFAULT 0, + "name" VARCHAR(45) NULL DEFAULT 'User', + "surname" VARCHAR(45) NULL, + "username" VARCHAR(45) NULL, + "lastSeen" TIMESTAMP NOT NULL, + "firstUsage" TIMESTAMP NOT NULL, + PRIMARY KEY ("id")); +CHECKSUM = 1 + +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."VideoInformation" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."VideoInformation" ( + "id" VARCHAR(11) NOT NULL, + "title" VARCHAR(100) NOT NULL, + "channel" VARCHAR(60) NOT NULL, + PRIMARY KEY ("id")); +CHECKSUM = 1; + +-- SHOW WARNINGS; +-- +-- SHOW WARNINGS; + +-- ----------------------------------------------------- +-- Table "youtubemd"."VideoStatistics" +-- ----------------------------------------------------- +CREATE TABLE "youtubemd"."VideoStatistics" ( + "timesRequested" INT NOT NULL DEFAULT 0, + "VideoInformation_id" VARCHAR(11) NOT NULL, + PRIMARY KEY ("VideoInformation_id"), + CONSTRAINT "fk_VideoStatistics_VideoInformation1" + FOREIGN KEY ("VideoInformation_id") + REFERENCES "youtubemd"."VideoInformation" ("id") + ON DELETE NO ACTION + ON UPDATE NO ACTION); +CHECKSUM = 1; + +-- SHOW WARNINGS; +-- +-- SET SQL_MODE=@OLD_SQL_MODE; +-- SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +-- SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; +DROP SEQUENCE IF EXISTS "youtubemd"."Metadata_idMetadata_sequence"; +CREATE SEQUENCE "youtubemd"."Metadata_idMetadata_sequence"; +ALTER TABLE "youtubemd"."Metadata" ALTER COLUMN "idMetadata" SET DEFAULT NEXTVAL('"youtubemd"."Metadata_idMetadata_sequence"'); +DROP SEQUENCE IF EXISTS "youtubemd"."Metadata__sequence"; +CREATE SEQUENCE "youtubemd"."Metadata__sequence"; +ALTER TABLE "youtubemd"."Metadata" ALTER COLUMN "" SET DEFAULT NEXTVAL('"youtubemd"."Metadata__sequence"'); diff --git a/Design/Database/new_database_model.mwb b/Design/Database/new_database_model.mwb new file mode 100644 index 0000000000000000000000000000000000000000..8a3992ea4f2937bf9f55e157056036d8cd1b8684 GIT binary patch literal 11153 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 O44rsLY&=Yi;Qt#ic2au) literal 0 HcmV?d00001 diff --git a/Design/Database/psql_model.sql b/Design/Database/psql_model.sql new file mode 100644 index 0000000..7fffb89 --- /dev/null +++ b/Design/Database/psql_model.sql @@ -0,0 +1,266 @@ +-- PostgreSQL model for YouTubeMDBot application +-- Created by Javinator9889 - thu, 24 October, 2019 +-- Last modification: mon, 4 November, 2019 +-- Version 1.1 + +-- DROP schema - only for testing +-- DROP SCHEMA IF EXISTS youtubemd CASCADE; +-- DROP TYPE IF EXISTS AFORMAT; +-- DROP TYPE IF EXISTS aquality; +-- DROP TYPE IF EXISTS behaviour; + +-- Custom "enum" types +CREATE TYPE AFORMAT AS ENUM ('mp3', 'm4a', 'ogg'); +CREATE TYPE AQUALITY AS ENUM ('128k', '96k'); +CREATE TYPE BEHAVIOUR AS ENUM ('always', 'not_found', 'ask', 'never'); + +-- Create DB schema +CREATE SCHEMA IF NOT EXISTS youtubemd; + +-- --------------------------------------- +-- Table User -- +-- --------------------------------------- +CREATE TABLE IF NOT EXISTS youtubemd.User +( + "id" INT PRIMARY KEY NOT NULL, + "name" VARCHAR(45), + "tag" VARCHAR(45), + "lang" VARCHAR(2), + "first_access" date +); + +-- --------------------------------------------- +-- Table Preferences -- +-- --------------------------------------------- +CREATE TABLE IF NOT EXISTS youtubemd.Preferences +( + "audio_format" AFORMAT NOT NULL DEFAULT 'm4a', + "audio_quality" AQUALITY NOT NULL DEFAULT '128k', + "song_behaviour" BEHAVIOUR NOT NULL DEFAULT 'not_found', + "send_song_link" BOOLEAN DEFAULT False, + "user_id" INT NOT NULL, + PRIMARY KEY ("user_id"), + CONSTRAINT "fk_user_id" + FOREIGN KEY ("user_id") + REFERENCES youtubemd.User ("id") + ON DELETE CASCADE + ON UPDATE CASCADE +); + +-- ------------------------------------------ +-- Table YouTube -- +-- ------------------------------------------ +CREATE TABLE IF NOT EXISTS youtubemd.YouTube +( + "id" VARCHAR(11) UNIQUE NOT NULL, + "times_requested" INT NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +); + +-- ------------------------------------------ +-- Table Metadata -- +-- ------------------------------------------ +CREATE TABLE IF NOT EXISTS youtubemd.Metadata +( + "id" BIGSERIAL NOT NULL, + "artist" VARCHAR(45) NOT NULL, + "album" VARCHAR(45) NOT NULL, + "cover" BYTEA NOT NULL, + "release_id" VARCHAR(36), + "recording_id" VARCHAR(36), + "duration" INT, + "title" VARCHAR(45), + "custom_metadata" BOOLEAN, + PRIMARY KEY ("id") +); + +-- ---------------------------------------------------- +-- Relation between YouTube and Metadata -- +-- ---------------------------------------------------- +CREATE TABLE IF NOT EXISTS youtubemd.Video_Has_Metadata +( + "id" VARCHAR(11) NOT NULL, + "metadata_id" INT NOT NULL, + PRIMARY KEY ("id", "metadata_id"), + CONSTRAINT "fk_video_id" + FOREIGN KEY ("id") + REFERENCES youtubemd.YouTube ("id"), + CONSTRAINT "fk_metadata_id" + FOREIGN KEY ("metadata_id") + REFERENCES youtubemd.Metadata ("id") +); + +-- -------------------------------------- +-- Table File -- +-- -------------------------------------- +CREATE TABLE IF NOT EXISTS youtubemd.File +( + "id" VARCHAR(50) UNIQUE NOT NULL, + "metadata_id" INT UNIQUE NOT NULL, + "audio_quality" AQUALITY NOT NULL, + "size" INT, + PRIMARY KEY ("id", "metadata_id"), + CONSTRAINT "fk_metadata_id" + FOREIGN KEY ("metadata_id") + REFERENCES youtubemd.Metadata ("id") +); + +-- ----------------------------------------- +-- Table History -- +-- ----------------------------------------- +CREATE TABLE IF NOT EXISTS youtubemd.History +( + "id" BIGSERIAL NOT NULL, + "file_id" VARCHAR(50) NOT NULL, + "user_id" INT NOT NULL, + "metadata_id" INT NOT NULL, + "date" date, + PRIMARY KEY ("id", "file_id", "user_id", "metadata_id"), + CONSTRAINT "fk_user_id" + FOREIGN KEY ("user_id") + REFERENCES youtubemd.User ("id") + ON DELETE CASCADE + ON UPDATE CASCADE, + CONSTRAINT "fk_file_id" + FOREIGN KEY ("file_id") + REFERENCES youtubemd.File ("id"), + CONSTRAINT "fk_metadata_id" + FOREIGN KEY ("metadata_id") + REFERENCES youtubemd.Metadata ("id") +); + +-- ------------------------------------------ +-- Table Playlist -- +-- ------------------------------------------ +CREATE TABLE IF NOT EXISTS youtubemd.Playlist +( + "id" VARCHAR(22) NOT NULL UNIQUE, + PRIMARY KEY ("id") +); + +-- ---------------------------------------------- +-- Table YouTube stats -- +-- ---------------------------------------------- +CREATE TABLE IF NOT EXISTS youtubemd.YouTubeStats +( + "id" VARCHAR(11) NOT NULL UNIQUE, + "daily_requests" INT NOT NULL DEFAULT 1, + "weekly_requests" INT NOT NULL DEFAULT 1, + "monthly_requests" INT NOT NULL DEFAULT 1, + PRIMARY KEY ("id"), + CONSTRAINT "fk_youtube_id" + FOREIGN KEY ("id") + REFERENCES youtubemd.YouTube ("id") +); + +-- Additional indexes +CREATE INDEX youtubemd.user_preferences_ix ON youtubemd.Preferences ("user_id"); +CREATE INDEX youtubemd.video_metadata_ix ON youtubemd.Video_Has_Metadata ("id", "metadata_id"); +CREATE INDEX youtubemd.history_ix ON youtubemd.History ("id", "file_id", "user_id", "metadata_id"); + +-- Trigger that updated the different stats +CREATE FUNCTION youtubemd.process_stats() RETURNS trigger AS +$$ +DECLARE + daily_value INT; + weekly_value INT; + monthly_value INT; +BEGIN + IF (SELECT EXISTS(SELECT 1 FROM youtubemd.YouTubeStats WHERE youtubemd.YouTubeStats.id = NEW.id)) THEN + SELECT INTO daily_value, weekly_value, monthly_value youtubemd.YouTubeStats.daily_requests, + youtubemd.YouTubeStats.weekly_requests, + youtubemd.YouTubeStats.monthly_requests + FROM youtubemd.YouTubeStats + WHERE youtubemd.YouTubeStats.id = NEW.id; + daily_value = daily_value + 1; + weekly_value = weekly_value + 1; + monthly_value = monthly_value + 1; + + UPDATE youtubemd.YouTubeStats + SET daily_requests = daily_value, + weekly_requests = weekly_value, + monthly_requests = monthly_value + WHERE id = NEW.id; + ELSE + INSERT INTO youtubemd.YouTubeStats (id) VALUES (NEW.id); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Complementary functions with useful operations +CREATE FUNCTION youtubemd.top_10_daily() + RETURNS TABLE + ( + id VARCHAR(11), + daily_requests INT + ) +AS +$$ +BEGIN + RETURN QUERY SELECT DISTINCT youtubemd.YouTubeStats.id, youtubemd.YouTubeStats.daily_requests + FROM youtubemd.youtubestats + ORDER BY daily_requests DESC + FETCH FIRST 10 ROWS ONLY; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION youtubemd.top_10_weekly() + RETURNS TABLE + ( + id VARCHAR(11), + weekly_requests INT + ) +AS +$$ +BEGIN + RETURN QUERY SELECT DISTINCT youtubemd.YouTubeStats.id, youtubemd.YouTubeStats.weekly_requests + FROM youtubemd.youtubestats + ORDER BY weekly_requests DESC + FETCH FIRST 10 ROWS ONLY; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION youtubemd.top_10_monthly() + RETURNS TABLE + ( + id VARCHAR(11), + monthly_requests INT + ) +AS +$$ +BEGIN + RETURN QUERY SELECT DISTINCT youtubemd.YouTubeStats.id, youtubemd.YouTubeStats.monthly_requests + FROM youtubemd.youtubestats + ORDER BY monthly_requests DESC + FETCH FIRST 10 ROWS ONLY; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION youtubemd.clear_daily_stats() AS +$$ +BEGIN + UPDATE youtubemd.YouTubeStats SET daily_requests = 0; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION youtubemd.clear_weekly_stats() AS +$$ +BEGIN + UPDATE youtubemd.YouTubeStats SET weekly_requests = 0; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION youtubemd.clear_monthly_stats() AS +$$ +BEGIN + UPDATE youtubemd.YouTubeStats SET monthly_requests = 0; +END; +$$ LANGUAGE plpgsql; + +-- Init the trigger +CREATE TRIGGER stats_update + AFTER INSERT OR UPDATE + ON youtubemd.YouTube + FOR EACH ROW +EXECUTE PROCEDURE youtubemd.process_stats(); diff --git a/YouTubeMDBot/.idea/.gitignore b/YouTubeMDBot/.idea/.gitignore new file mode 100755 index 0000000..0e40fe8 --- /dev/null +++ b/YouTubeMDBot/.idea/.gitignore @@ -0,0 +1,3 @@ + +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/YouTubeMDBot/.idea/YouTubeMDBot.iml b/YouTubeMDBot/.idea/YouTubeMDBot.iml new file mode 100755 index 0000000..6711606 --- /dev/null +++ b/YouTubeMDBot/.idea/YouTubeMDBot.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/codeStyles/codeStyleConfig.xml b/YouTubeMDBot/.idea/codeStyles/codeStyleConfig.xml new file mode 100755 index 0000000..a55e7a1 --- /dev/null +++ b/YouTubeMDBot/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/dictionaries/javinator9889.xml b/YouTubeMDBot/.idea/dictionaries/javinator9889.xml new file mode 100755 index 0000000..787b6ce --- /dev/null +++ b/YouTubeMDBot/.idea/dictionaries/javinator9889.xml @@ -0,0 +1,10 @@ + + + + acoustid + ffmpeg + fpcalc + javinator + + + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/git_toolbox_prj.xml b/YouTubeMDBot/.idea/git_toolbox_prj.xml new file mode 100644 index 0000000..c7846c0 --- /dev/null +++ b/YouTubeMDBot/.idea/git_toolbox_prj.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/inspectionProfiles/profiles_settings.xml b/YouTubeMDBot/.idea/inspectionProfiles/profiles_settings.xml new file mode 100755 index 0000000..105ce2d --- /dev/null +++ b/YouTubeMDBot/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/misc.xml b/YouTubeMDBot/.idea/misc.xml new file mode 100755 index 0000000..8656114 --- /dev/null +++ b/YouTubeMDBot/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/modules.xml b/YouTubeMDBot/.idea/modules.xml new file mode 100755 index 0000000..48cc268 --- /dev/null +++ b/YouTubeMDBot/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/YouTubeMDBot/.idea/vcs.xml b/YouTubeMDBot/.idea/vcs.xml new file mode 100755 index 0000000..6c0b863 --- /dev/null +++ b/YouTubeMDBot/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file