Python编写PostgreSQL数据库结构比对程序(超详细源代码)

20 篇文章 2 订阅
1 篇文章 0 订阅


前言

随着PostgreSQL数据库越来越被广泛的应用,它的优势也逐渐被广泛的认可。但针对该数据库的工具却较少。由于工作需要,经常有对PostgreSQL数据库进行比对生成升级脚本的需求,故本人使用python编写了一个PostgreSQL数据库比对程序和大家共同交流学习。(文章的最后将提供源程序的下载地址)

一、功能介绍

比对程序实现了对两个数据库的结构进行比较并生成差异化更新脚本。
比对的内容如下:
1、表结构比比较:包含表、字段、字段注释、序列、约束、索引、触发器的比较。
2、视图比较:包含普通视图和物化视图的比较,并生成创建或者修改脚本。
3、函数比较:函数(存储过程)的比较,并生成新建或者修改脚本。
4、外部表比较:外部表进行比较,并生成新建或者修改脚本。

二、代码目录结构

1、下图为代码的目录结构展开截图及说明
在这里插入图片描述

2、下面为程序运行界面
在这里插入图片描述

3、下图为生成差异化脚本updScript.sql文件部分片段
![在这里插入图片描述](https://img-blog.csdnimg.cn/4e8ac673b570483aadc7ce7ff7694b23.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L21hMjg2Mzg4MzA5,size_16,color_FFFFFF,t_70

三、核心源代码解析

1、bin目录下main.py

import core.schemaSync as core
import PySimpleGUI as sg
import time

# 定义需要执行的任务
my_list = ['表', '视图', '函数', '外部表']
# 定义进度条
progressbar = [[sg.ProgressBar(len(my_list), orientation='h', size=(51, 10), bar_color=('#f47920', '#46485f'), key='progressbar')]]
# 定义输出窗口尺寸
output_win = [[sg.Output(size=(78, 20))]]
# 窗口布局
layout = [[sg.Frame('执行进度', layout=progressbar)],
          [sg.Frame('输出信息', layout=output_win)],
          [sg.Submit('开 始'), sg.Cancel('退 出')]]
window = sg.Window('PostgreSQL数据库比较程序', layout)
progress_bar = window['progressbar']
while True:
    event, values = window.read(timeout=10)
    # 点击按钮退出
    if event == '退 出' or event is None:
        break
    # 点击按钮开始
    elif event == '开 始':
        file_name = ''
        # 循环要执行的任务
        for i, item in enumerate(my_list):
            if i == 0:
                print(item + ' 比对开始...')
                file_name = core.sync_table()
                print(item+' 比对完成!(含字段及注释、序列、约束、索引、触发器的比对)\n')
            if i == 1:
                print(item + ' 比对开始...')
                core.sync_view()
                print(item+' 比对完成!(含普通视图、物化视图)\n')
            if i == 2:
                print(item + ' 比对开始...')
                core.sync_function()
                print(item+' 比对完成!\n')
            if i == 3:
                print(item + ' 比对开始...')
                core.sync_foreign_table()
                print(item+' 比对完成!\n')
            time.sleep(1)
            # 更新进度条
            progress_bar.UpdateBar(i + 1)
        # 输出生成脚本的路径
        print('差异脚本路径:' + file_name)

2、conf目录下config.ini

[database]
#postgre_src
pg_host_src=192.168.10.70
pg_port_src=5432
pg_user_src=postgres
pg_pwd_src=***
pg_db_src=data_use

#postgre_dst
pg_host_dst = 192.168.10.71
pg_port_dst=5432
pg_user_dst=postgres
pg_pwd_dst=***
pg_db_dst=data_use

[common]
output_file_name=updScript.sql
log_dir=

3、core目录下的schemaSync.py

import os
from util.connDB import getConn
from util.logger import log
import sqlConfig
from util.commonFunction import commonFunction
import util.readConfig as readConfig


# 对比表、字段及注释、序列、约束、索引、触发器
def sync_table():
    try:
        # 获取源数据库连接
        global sourceConn,targetConn,sourceCursor,targetCursor
        sourceConn, sourceCursor = getConn('postgre_src')
        # 获取目标数据库连接
        targetConn, targetCursor = getConn('postgre_dst')
        # 求源和目标表的差集
        sourceCursor.execute(sqlConfig.selectTable)
        targetCursor.execute(sqlConfig.selectTable)
        sourceTable = sourceCursor.fetchall()
        targetTable = targetCursor.fetchall()
        tableReslut=set(sourceTable) - set(targetTable)
        tableName = ''
        for item in tableReslut:
            tableName=tableName+','+item[0]
        tableName=str(tableName[1:])
        # print(tableName)
        # 导出新增表
        cf=commonFunction()
        file_name=cf.dumpTable(tableName)

        # 对比序列
        # 求源和目标序列的差集
        sourceCursor.execute(sqlConfig.selectSequence % tableName.replace(",", "','"))
        targetCursor.execute(sqlConfig.selectSequence % tableName.replace(",", "','"))
        sourceSequence = sourceCursor.fetchall()
        targetSequence = targetCursor.fetchall()
        sequenceReslut = list(set(sourceSequence) - set(targetSequence))
        # print(sequenceReslut)
        for itemReslut in sequenceReslut:
            sequence_name = itemReslut[0]
            if (sequence_name != ''):
                str_sql = "create sequence " + sequence_name + ";"
                # print(str_sql)
                # 追加写入到指定文件
                with open(file_name, mode='a+', encoding="utf-8") as w:
                    w.write("-- Sequence compares" + "\n" + str_sql + "\n")

        # 对比字段
        # 求源和目标字段的差集
        sourceCursor.execute(sqlConfig.selectColumn % tableName.replace(",","','"))
        targetCursor.execute(sqlConfig.selectColumn % tableName.replace(",","','"))
        sourceColumn = sourceCursor.fetchall()
        targetColumn = targetCursor.fetchall()
        columnReslut = list(set(sourceColumn) - set(targetColumn))
        # 对列表根据表名称和字段名称排序
        columnReslut.sort()
        #print(columnReslut)
        if(len(columnReslut)>0):
            # 追加写入到指定文件
            with open(file_name, mode='a+', encoding="utf-8") as w:
                w.write("\n--\n-- Column compares\n--\n" + "\n")
        # 外循环,对比后的差集字段
        for itemReslut in columnReslut:
            table_name = itemReslut[0]
            column_name = itemReslut[1]
            column_type = itemReslut[2]
            column_default = itemReslut[3]
            description = itemReslut[4]
            # print(itemReslut[0],itemReslut[1],itemReslut[2],itemReslut[3],itemReslut[4],itemReslut[5])
            # 内循环,目标库的字段
            column_exists = 0
            for itemTarget in targetColumn:
                # 表名称和字段名称相等
                if(table_name == itemTarget[0] and column_name == itemTarget[1]):
                    column_exists = column_exists+1
                    # 字段类型不等
                    if(column_type != itemTarget[2]):
                        #print("alter table " + table_name + "  alter column " + column_name + " type " + column_type + ";")
                        str_sql = "alter table " + table_name + "  alter column " + column_name + " type " + column_type + ";\n"
                        # 追加写入
                        with open(file_name, mode='a+', encoding="utf-8") as w:
                            w.write(str_sql + "\n")
                    # 描述不等
                    if(description != itemTarget[4] and description != ''):
                        #print("comment on column " + table_name + "." + column_name + " is '" + description + "';")
                        str_sql = "comment on column " + table_name + "." + column_name + " is '" + description + "';\n"
                        # 追加写入
                        with open(file_name, mode='a+', encoding="utf-8") as w:
                            w.write(str_sql + "\n")
                    # 默认值不等
                    if (column_default != itemTarget[3] and column_default != ''):
                        # print("alter table " + table_name + " alter column " + column_name + " set DEFAULT " + column_default + ";")
                        str_sql = "alter table " + table_name + " alter column " + column_name + " set DEFAULT " + column_default + ";\n"
                        #追加写入
                        with open(file_name, mode='a+', encoding="utf-8") as w:
                            w.write(str_sql + "\n")

            # 若字段名称不存在,则新增字段
            if(column_exists == 0):
                # 判断新增字段的默认值是否为空
                if (column_default != ''):
                    #print("alter table " + table_name + "  add " + column_name + " " + column_type + " default " + column_default + ";")
                    str_sql="alter table " + table_name + "  add " + column_name + " " + column_type + " default " + column_default + ";\n"
                else:
                    #print("alter table " + table_name + "  add " + column_name + " " + column_type + ";")
                    str_sql="alter table " + table_name + "  add " + column_name + " " + column_type + ";\n"
                with open(file_name, mode='a+', encoding="utf-8") as w:
                    w.write(str_sql + "\n")
                # 判断新增字段的comment是否为空
                if (description != ''):
                    #print("comment on column " + table_name + "." + column_name + " is '" + description + "';")
                    str_sql="comment on column " + table_name + "." + column_name + " is '" + description + "';\n"
                    with open(file_name, mode='a+', encoding="utf-8") as w:
                        w.write(str_sql + "\n")
        # 对比约束
        # 求源和目标约束的差集
        sourceCursor.execute(sqlConfig.selectConstraint % tableName.replace(",", "','"))
        targetCursor.execute(sqlConfig.selectConstraint % tableName.replace(",", "','"))
        sourceConstraint = sourceCursor.fetchall()
        targetConstraint = targetCursor.fetchall()
        constraintReslut = list(set(sourceConstraint) - set(targetConstraint))
        # print(constraintReslut)
        if (len(constraintReslut) > 0):
            # 追加写入到指定文件
            with open(file_name, mode='a+', encoding="utf-8") as w:
                w.write("\n--\n-- Constraint compares\n--\n" + "\n")
        # 外循环,对比后的差集约束
        for itemReslut in constraintReslut:
            constraint_name = itemReslut[0]
            table_name = itemReslut[1]
            column_name = itemReslut[2]
            foreign_table_name = itemReslut[3]
            foreign_column_name = itemReslut[4]
            constraint_type = itemReslut[5]
            check_value = itemReslut[6]
            #print(targetConstraint)
            # 内循环,目标库的约束
            constraint_exists = 0
            for itemTarget in targetConstraint:
                # 约束名称相等,则不处理
                if(constraint_name == itemTarget[0]):
                    constraint_exists=constraint_exists+1
                # 约束名称不等、约束类型、表名称相等、字段名称相等,则不处理)
                if(constraint_name != itemTarget[0] and constraint_type == itemTarget[5] and table_name == itemTarget[1] and column_name == itemTarget[2]):
                    constraint_exists = constraint_exists + 1
            # 若约束名称不存在,#constraint_type有四种:UNIQUE、PRIMARY KEY、CHECK、FOREIGN KEY
            if(constraint_exists == 0):
                if(constraint_type == "PRIMARY KEY"):
                    str_sql = 'alter table ' + table_name + ' add constraint "' + constraint_name + '" primary key(' + column_name + ');'
                elif(constraint_type == "FOREIGN KEY"):
                    str_sql = 'alter table ' + table_name + ' add constraint "' + constraint_name + '" foreign key(' + column_name + ') references ' + foreign_table_name + '(' + foreign_column_name + ');'
                elif(constraint_type == "UNIQUE"):
                    str_sql = 'alter table ' + table_name + ' add constraint "' + constraint_name + '" unique(' + column_name + ');'
                elif(constraint_type == "CHECK"):
                    str_sql = 'alter table ' + table_name + ' add constraint "' + constraint_name + '" check' + check_value + ';'
                # 追加写入到指定文件
                with open(file_name, mode='a+', encoding="utf-8") as w:
                    w.write(str_sql + "\n")

        # 对比索引
        # 求源和目标索引的差集
        sourceCursor.execute(sqlConfig.selectIndex % tableName.replace(",", "','"))
        targetCursor.execute(sqlConfig.selectIndex % tableName.replace(",", "','"))
        sourceIndex = sourceCursor.fetchall()
        targetIndex = targetCursor.fetchall()
        indexReslut = list(set(sourceIndex) - set(targetIndex))
        #print(indexReslut)
        if (len(indexReslut) > 0):
            # 追加写入到指定文件
            with open(file_name, mode='a+', encoding="utf-8") as w:
                w.write("\n--\n-- Index compares\n--\n" + "\n")
        for itemReslut in indexReslut:
            indexname = itemReslut[1]
            indexdef = itemReslut[2]
            if (indexname != ''):
                str_sql = indexdef
                # print(str_sql)
                # 追加写入到指定文件
                with open(file_name, mode='a+', encoding="utf-8") as w:
                    w.write("-- Create index" + "\n" + str_sql + "\n")
        # 对比触发器
        # 求源和目标触发器的差集
        sourceCursor.execute(sqlConfig.selectTrigger % tableName.replace(",", "','"))
        targetCursor.execute(sqlConfig.selectTrigger % tableName.replace(",", "','"))
        sourceTrigger = sourceCursor.fetchall()
        targetTrigger = targetCursor.fetchall()
        triggerReslut = list(set(sourceTrigger) - set(targetTrigger))
        # print(triggerReslut)
        if (len(triggerReslut) > 0):
            # 追加写入到指定文件
            with open(file_name, mode='a+', encoding="utf-8") as w:
                w.write("\n--\n-- Trigger compares\n--\n" + "\n")
        #外循环,对比差集触发器
        for itemReslut in triggerReslut:
            trigger_name = itemReslut[0]
            event_object_table = itemReslut[1]
            definition = itemReslut[2]
            drop_definition = itemReslut[3]
            # 内循环,目标库的触发器
            trigger_exists = 0
            for itemTarget in targetTrigger:
                # 触发器名称、表名称都相等
                if (trigger_name == itemTarget[0] and event_object_table == itemTarget[1]):
                    trigger_exists = trigger_exists + 1
                    str_sql = drop_definition+'\n'+definition
                    # 追加写入到指定文件
                    with open(file_name, mode='a+', encoding="utf-8") as w:
                        w.write("\n--\n-- Alter trigger\n--\n" + str_sql+"\n")

            # 触发器名称不存在
            if (trigger_exists == 0):
                str_sql = definition
                # 追加写入到指定文件
                with open(file_name, mode='a+', encoding="utf-8") as w:
                    w.write("\n--\n-- Create trigger\n--\n" + str_sql + "\n")

        return file_name

    except Exception as e:
        # 异常回滚
        targetConn.rollback()
        log.error(str(e))
        #raise e
    finally:
        # 关闭数据库
        sourceCursor.close()
        sourceConn.close()
        targetCursor.close()
        targetConn.close()

#对比普通视图、物化视图
def sync_view():
    try:
        # 获取生成文件路径及名称
        data = readConfig.readConfig()
        output_file_name = data.getFileName("output_file_name")
        root_dir = os.path.dirname(os.path.dirname(os.path.abspath('__file__')))
        output_path = os.path.join(root_dir, "output\\")
        output_file = output_file_name
        #print(output_path+output_file)
        # 获取源数据库连接
        global sourceConn,targetConn,sourceCursor,targetCursor
        sourceConn, sourceCursor = getConn('postgre_src')
        # 获取目标数据库连接
        targetConn, targetCursor = getConn('postgre_dst')
        # 对比源库和目标库视图
        sourceCursor.execute(sqlConfig.selectView)
        targetCursor.execute(sqlConfig.selectView)
        sourceView = sourceCursor.fetchall()
        targetView = targetCursor.fetchall()
        viewReslut = list(set(sourceView) - set(targetView))
        # print(viewReslut)
        # 外循环,循环视图差集
        for itemReslut in viewReslut:
            view_name = itemReslut[0]
            definition = itemReslut[1]
            view_type = itemReslut[2]
            #内循环,目标库视图
            view_exists = 0
            for itemTarget in targetView:
                # 视图名称相同
                if (view_name==itemTarget[0]):
                    view_exists=view_exists+1
                    # 判断视图定义是否相同
                    if(definition != itemTarget[1]):
                        # 修改普通视图
                        if(view_type=='view'):
                            str_sql='create or replace view ' + view_name + ' as \n' + definition
                            #print(str_sql)
                        # 修改物化视图
                        else:
                            str_sql = 'drop materialized view '+view_name+';\ncreate materialized view ' + view_name + ' as \n' + definition
                            #print(str_sql)
                        # 追加写入到指定文件
                        with open(output_path+output_file, mode='a+', encoding="utf-8") as w:
                            w.write("\n--\n-- Alter view or materialized view\n--\n" + "\n" + str_sql + "\n")
            # 视图名称不存在,创建视图
            if(view_exists==0):
                # 创建普通视图
                if (view_type == 'view'):
                    str_sql = 'create view or materialized view' + view_name + ' as \n' + definition
                    #print(str_sql)
                # 创建物化视图
                else:
                    str_sql = 'create materialized view ' + view_name + ' as \n' + definition
                    #print(str_sql)
                # 追加写入到指定文件
                with open(output_path + output_file, mode='a+', encoding="utf-8") as w:
                    w.write("\n--\n-- Create view or materialized view\n--\n" + "\n" + str_sql + "\n")

    except Exception as e:
        # 异常回滚
        targetConn.rollback()
        log.error(str(e))
        #raise e
    finally:
        # 关闭数据库
        sourceCursor.close()
        sourceConn.close()
        targetCursor.close()
        targetConn.close()

# 对比函数
def sync_function():
    try:
        # 获取生成文件路径及名称
        data = readConfig.readConfig()
        output_file_name = data.getFileName("output_file_name")
        root_dir = os.path.dirname(os.path.dirname(os.path.abspath('__file__')))
        output_path = os.path.join(root_dir, "output\\")
        output_file = output_file_name
        #print(output_path+output_file)
        # 获取源数据库连接
        global sourceConn,targetConn,sourceCursor,targetCursor
        sourceConn, sourceCursor = getConn('postgre_src')
        # 获取目标数据库连接
        targetConn, targetCursor = getConn('postgre_dst')
        # 对比源库和目标库函数
        sourceCursor.execute(sqlConfig.selectFunction)
        targetCursor.execute(sqlConfig.selectFunction)
        sourceFunction = sourceCursor.fetchall()
        targetFunction = targetCursor.fetchall()
        functionReslut = list(set(sourceFunction) - set(targetFunction))
        # print(viewReslut)
        # 外循环,循环函数差集
        for itemReslut in functionReslut:
            routine_name = itemReslut[0]
            routine_definition = itemReslut[1]
            full_definition = itemReslut[2]
            # 内循环,目标库函数
            function_exists = 0
            for itemTarget in targetFunction:
                # 函数名称相同
                if (routine_name==itemTarget[0]):
                    function_exists=function_exists+1
                    # 判断函数定义是否相同
                    if(routine_definition != itemTarget[1]):
                        # 修改函数
                        # 追加写入到指定文件
                        str_sql = full_definition
                        with open(output_path+output_file, mode='a+', encoding="utf-8") as w:
                            w.write("\n--\n-- Alter function\n--\n" + "\n" + str_sql + "\n")
            # 函数名称不存在,创建函数
            if(function_exists==0):
                # 创建函数
                # 追加写入到指定文件
                str_sql = full_definition
                with open(output_path + output_file, mode='a+', encoding="utf-8") as w:
                    w.write("\n--\n-- Create function\n--\n" + "\n" + str_sql + "\n")

    except Exception as e:
        # 异常回滚
        targetConn.rollback()
        log.error(str(e))
        #raise e
    finally:
        # 关闭数据库
        sourceCursor.close()
        sourceConn.close()
        targetCursor.close()
        targetConn.close()
#对比外部表
def sync_foreign_table():
    try:
        # 获取生成文件路径及名称
        data = readConfig.readConfig()
        output_file_name = data.getFileName("output_file_name")
        root_dir = os.path.dirname(os.path.dirname(os.path.abspath('__file__')))
        output_path = os.path.join(root_dir, "output\\")
        output_file = output_file_name
        #print(output_path+output_file)
        # 获取源数据库连接
        global sourceConn,targetConn,sourceCursor,targetCursor
        sourceConn, sourceCursor = getConn('postgre_src')
        # 获取目标数据库连接
        targetConn, targetCursor = getConn('postgre_dst')
        # 对比源库和目标库外部表
        sourceCursor.execute(sqlConfig.selectForeignTable)
        targetCursor.execute(sqlConfig.selectForeignTable)
        sourceForeignTable = sourceCursor.fetchall()
        targetForeignTable = targetCursor.fetchall()
        foreignTableReslut = list(set(sourceForeignTable) - set(targetForeignTable))
        # print(foreignTableReslut)
        # 外循环,循环外部表差集
        for itemReslut in foreignTableReslut:
            table_name = itemReslut[0]
            column_name = itemReslut[1]
            definition = itemReslut[2]
            #内循环,目标库外部表
            foreign_exists = 0
            for itemTarget in targetForeignTable:
                # 外部表名称相同
                if (table_name==itemTarget[0]):
                    foreign_exists=foreign_exists+1
                    # 判断外部表字段是否相同
                    if(column_name != itemTarget[1]):
                        # 修改普通视图
                        str_sql = 'drop FOREIGN TABLE '+ table_name +';\n' + definition
                        # 追加写入到指定文件
                        with open(output_path+output_file, mode='a+', encoding="utf-8") as w:
                            w.write("\n--\n-- Alter foreign table\n--\n" + "\n" + str_sql + "\n")
            # 外部表名称不存在,创建外部表
            if(foreign_exists==0):
                # 创建sql
                str_sql = definition
                # 追加写入到指定文件
                with open(output_path + output_file, mode='a+', encoding="utf-8") as w:
                    w.write("\n--\n-- Create foreign table\n--\n" + "\n" + str_sql + "\n")

    except Exception as e:
        # 异常回滚
        targetConn.rollback()
        log.error(str(e))
        #raise e
    finally:
        # 关闭数据库
        sourceCursor.close()
        sourceConn.close()
        targetCursor.close()
        targetConn.close()

4、core目录下的sqlConfig.py

# 查询表信息
selectTable = """select tablename from pg_tables where schemaname='public';"""
# 查询序列信息
selectSequence = """select sequence_name
                    from information_schema.sequences
                    where 'nextval(''' || sequence_name || '''::regclass)' not in
                          (select column_default
                            from information_schema.columns
                            where column_default is not null
                            and table_name in ('%s'));"""
# 查询字段信息
selectColumn = """select a.table_name,
                       a.column_name,
                       (case
                            when substr(b.data_type, 1, 18) = 'character varying('
                                then replace(b.data_type, 'character varying(', 'varchar(')
                            when substr(b.data_type, 1, 10) = 'character('
                                then replace(b.data_type, 'character(', 'char(')
                            when b.data_type = 'timestamp without time zone'
                                then 'timestamp'
                            else b.data_type end)     as column_type,
                       coalesce(a.column_default, '') as column_default,
                       coalesce(b.description, '')    as description
                from information_schema.columns a
                         left join
                     (SELECT c.relname                            as table_name,
                             format_type(a.atttypid, a.atttypmod) as data_type,
                             a.attname                            as column_name,
                             d.description
                      FROM pg_class as c
                               join
                           pg_attribute as a on a.attrelid = c.oid
                               left join
                           pg_description d on d.objoid = a.attrelid and d.objsubid = a.attnum
                      where a.attnum > 0
                     ) b
                     on a.table_name = b.table_name and a.column_name = b.column_name
                where a.table_schema = 'public'
                  and a.is_updatable = 'YES'
                  and a.table_name in (select tablename from pg_tables where schemaname = 'public' and tablename not in ('%s'));"""
# 查询约束信息
selectConstraint = """SELECT tc.constraint_name,
                       tc.table_name,
                       coalesce(kcu.column_name,'') as column_name,
                       ccu.table_name  AS foreign_table_name,
                       ccu.column_name AS foreign_column_name,
                       tc.constraint_type,
                       coalesce(co.consrc,'') as check_value
                FROM information_schema.table_constraints AS tc
                         left JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name
                         JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name
                         left join pg_constraint co on co.conname = tc.constraint_name
                WHERE tc.table_name not in ('%s');"""
# 查询索引信息
selectIndex = """select tablename, indexname, indexdef||';' as indexdef
                from pg_indexes
                WHERE schemaname = 'public'
                  and indexname not in (select conname from pg_constraint where contype in ('p', 'f', 'u'))
                  and tablename not in ('%s');"""
# 查询视图信息
selectView = """select viewname as view_name, pg_get_viewdef(viewname, true) as definition, 'view' as view_type
                from pg_views
                where schemaname = 'public'
                union all
                select matviewname as view_name, pg_get_viewdef(matviewname, true) as definition, 'matview' as view_type
                from pg_matviews
                where schemaname = 'public';"""
# 查询函数信息
selectFunction = """SELECT a.routine_name,
                   a.routine_definition,
                   'create or replace function ' || a.routine_name || '(' ||
                   string_agg(coalesce(b.parameter_name || ' ', '') || coalesce(b.data_type, ''), ',' order by ordinal_position) ||
                   E')\n\t' || ' returns ' || a.data_type || E'\nas\n$$' || a.routine_definition || '$$ LANGUAGE ' ||
                   external_language || ';' as full_definition
            FROM information_schema.routines a
                     left JOIN information_schema.parameters b ON a.specific_name = b.specific_name
            where a.specific_schema = 'public'
            group by a.specific_name, a.routine_name, a.data_type, a.external_language, a.routine_definition;"""
# 查询外部表
selectForeignTable = """select table_name,string_agg(column_name,',' order by attnum) as column_name,'create FOREIGN TABLE '||table_name||E'\n(\n\t'||string_agg(column_name||' '||data_type,E',\n\t')||E'\n)\n'||ftoptions as definition from
                        (SELECT b.relname                            as table_name,
                               format_type(c.atttypid, c.atttypmod) as data_type,
                               c.attname                            as column_name,
                                d.srvname,
                                'SERVER '||d.srvname||E'\nOPTIONS '||'('||replace(replace(array_to_string(a.ftoptions,','),'=',' '''),',',''',')||''');' as ftoptions,
                                c.attnum
                        FROM pg_foreign_table a
                                 join
                             pg_class as b on a.ftrelid = b.oid
                                 join
                             pg_attribute as c on c.attrelid = b.oid
                                join
                        pg_foreign_server d on a.ftserver=d.oid
                        where c.attnum > 0) t group by t.table_name,t.ftoptions;"""
# 查询触发器信息
selectTrigger = """select b.trigger_name,
                   b.event_object_table,
                   case
                       when a.tgenabled = 'D' then 'create trigger ' || b.trigger_name || ' ' || b.action_timing || ' ' ||
                                                   string_agg(b.event_manipulation, ' or ') || ' on ' || b.event_object_table ||
                                                   ' for each ' || b.action_orientation || ' ' || b.action_statement || E';\n' ||
                                                   'alter table ' || b.event_object_table || ' disable trigger ' ||
                                                   b.trigger_name || ';'
                       else 'create trigger ' || b.trigger_name || ' ' || b.action_timing || ' ' ||
                            string_agg(b.event_manipulation, ' or ') || ' on ' || b.event_object_table || ' for each ' ||
                            b.action_orientation || ' ' || b.action_statement || ';' end as definition,
                   'drop trigger if exists '||trigger_name||' on '||b.event_object_table||';' as drop_definition
            from (select tgname, relname, tgenabled
                  from pg_trigger tr
                           join pg_class cl on tr.tgrelid = cl.oid) a
                     join information_schema.triggers b on a.tgname = b.trigger_name and a.relname = b.event_object_table
            where b.trigger_schema='public' and b.event_object_table not in ('%s')
            group by a.tgenabled, b.trigger_name, b.event_object_table, b.action_statement, b.action_orientation, b.action_timing;"""

5、util目录为连接数据库、读取配置文件、生成日志的代码,代码较为简单暂不罗列。

四、完整源代码项目下载


Python编写PostgreSQL数据库结构比对程序源代码

下载地址:https://download.csdn.net/download/ma286388309/20687085

总结

以上为比对程序的核心代码,供大家交流学习。程序如遇到bug或不完善的地方请留言指正,我也好继续完善。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小马穿云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值