Merge pull request #11226 from opf/maintenance/timestamp-migration

Migrate all datetime columns to timestampz
pull/11257/head
Oliver Günther 2 years ago committed by GitHub
commit 422878d575
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      config/initializers/postgresql_timestamp.rb
  2. 49
      db/migrate/20220831081937_migrate_timestamps_to_with_timezone.rb
  3. 4
      modules/budgets/spec/lib/api/v3/budgets/budget_representer_spec.rb
  4. 6
      modules/meeting/app/models/meeting.rb
  5. 105
      modules/meeting/spec/models/meeting_spec.rb
  6. 2
      spec/controllers/account_controller_spec.rb
  7. 4
      spec/factories/user_factory.rb
  8. 2
      spec/models/activities/fetcher_integration_spec.rb
  9. 19
      spec/models/activities/work_package_activity_provider_spec.rb
  10. 2
      spec/models/repository/git_spec.rb
  11. 2
      spec/models/repository/subversion_spec.rb
  12. 12
      spec_legacy/fixtures/messages.yml
  13. 44
      spec_legacy/fixtures/work_packages.yml

@ -0,0 +1,2 @@
# Use timestampz to create new timestamp columns, so that we get WITH TIME ZONE support
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz

@ -0,0 +1,49 @@
class MigrateTimestampsToWithTimezone < ActiveRecord::Migration[7.0]
def up
migrate_to_timestampz
end
def down
migrate_to_timestamp
end
private
def migrate_to_timestampz
execute <<~SQL.squish
DO $$
DECLARE
t record;
BEGIN
FOR t IN
SELECT column_name, table_name, data_type
FROM information_schema.columns
WHERE
table_schema = ANY (SELECT unnest(string_to_array(replace(setting, '"$user"', CURRENT_USER), ', ')) FROM pg_settings WHERE name = 'search_path')
AND data_type = 'timestamp without time zone'
LOOP
EXECUTE 'ALTER TABLE ' || t.table_name || ' ALTER COLUMN ' || t.column_name || ' TYPE timestamp with time zone USING ' || t.column_name || ' AT TIME ZONE ''UTC''';
END LOOP;
END$$;
SQL
end
def migrate_to_timestamp
execute <<~SQL.squish
DO $$
DECLARE
t record;
BEGIN
FOR t IN
SELECT column_name, table_name, data_type
FROM information_schema.columns
WHERE
table_schema = ANY (SELECT unnest(string_to_array(replace(setting, '"$user"', CURRENT_USER), ', ')) FROM pg_settings WHERE name = 'search_path')
AND data_type = 'timestamp with time zone'
LOOP
EXECUTE 'ALTER TABLE ' || t.table_name || ' ALTER COLUMN ' || t.column_name || ' TYPE timestamp without time zone USING ' || t.column_name || ' AT TIME ZONE ''UTC''';
END LOOP;
END$$;
SQL
end
end

@ -36,14 +36,14 @@ describe ::API::V3::Budgets::BudgetRepresenter do
create(:user,
member_in_project: project,
created_at: 1.day.ago,
updated_at: Date.today)
updated_at: Time.zone.now)
end
let(:budget) do
create(:budget,
author: user,
project:,
created_at: 1.day.ago,
updated_at: Date.today)
updated_at: Time.zone.now)
end
let(:representer) { described_class.new(budget, current_user: user) }

@ -99,6 +99,10 @@ class Meeting < ApplicationRecord
end
end
def start_time=(value)
super value&.to_datetime
end
def start_month
start_time.month
end
@ -239,7 +243,7 @@ class Meeting < ApplicationRecord
end
##
# Determines whether new raw values werde provided.
# Determines whether new raw values were provided.
def parse_start_time?
!(changed & %w(start_date start_time_hour)).empty?
end

@ -29,9 +29,9 @@
require File.dirname(__FILE__) + '/../spec_helper'
describe Meeting, type: :model do
shared_let (:user1) { create(:user) }
shared_let (:user2) { create(:user) }
let(:project) { create(:project, members: project_members) }
let(:user1) { create(:user) }
let(:user2) { create(:user) }
let(:meeting) { create(:meeting, project:, author: user1) }
let(:agenda) do
meeting.create_agenda text: 'Meeting Agenda text'
@ -41,71 +41,70 @@ describe Meeting, type: :model do
let(:role) { create(:role, permissions: [:view_meetings]) }
before do
@m = build :meeting, title: 'dingens'
end
it { is_expected.to belong_to :project }
it { is_expected.to belong_to :author }
it { is_expected.to validate_presence_of :title }
describe 'to_s' do
it { expect(@m.to_s).to eq('dingens') }
end
describe 'start_date' do
it { expect(@m.start_date).to eq(Date.tomorrow.iso8601) }
end
describe 'start_month' do
it { expect(@m.start_month).to eq(Date.tomorrow.month) }
end
describe 'new instance' do
let(:meeting) { build :meeting, title: 'dingens' }
describe 'start_year' do
it { expect(@m.start_year).to eq(Date.tomorrow.year) }
end
describe 'end_time' do
it { expect(@m.end_time).to eq(Date.tomorrow + 11.hours) }
end
describe 'to_s' do
it { expect(meeting.to_s).to eq('dingens') }
end
describe 'date validations' do
it 'marks invalid start dates' do
@m.start_date = '-'
expect(@m.start_date).to eq('-')
expect { @m.start_time }.to raise_error(ArgumentError)
expect(@m).not_to be_valid
expect(@m.errors.count).to eq(1)
describe 'start_date' do
it { expect(meeting.start_date).to eq(Date.tomorrow.iso8601) }
end
it 'marks invalid start hours' do
@m.start_time_hour = '-'
expect(@m.start_time_hour).to eq('-')
expect { @m.start_time }.to raise_error(ArgumentError)
expect(@m).not_to be_valid
expect(@m.errors.count).to eq(1)
describe 'start_month' do
it { expect(meeting.start_month).to eq(Date.tomorrow.month) }
end
it 'is not invalid when setting date_time explicitly' do
@m.start_time = DateTime.now
expect(@m).to be_valid
describe 'start_year' do
it { expect(meeting.start_year).to eq(Date.tomorrow.year) }
end
it 'is invalid when setting date_time wrong' do
@m.start_time = '-'
expect(@m).not_to be_valid
describe 'end_time' do
it { expect(meeting.end_time).to eq(Date.tomorrow + 11.hours) }
end
it 'accepts changes after invalid dates' do
@m.start_date = '-'
expect { @m.start_time }.to raise_error(ArgumentError)
expect(@m).not_to be_valid
describe 'date validations' do
it 'marks invalid start dates' do
meeting.start_date = '-'
expect(meeting.start_date).to eq('-')
expect { meeting.start_time }.to raise_error(ArgumentError)
expect(meeting).not_to be_valid
expect(meeting.errors.count).to eq(1)
end
it 'marks invalid start hours' do
meeting.start_time_hour = '-'
expect(meeting.start_time_hour).to eq('-')
expect { meeting.start_time }.to raise_error(ArgumentError)
expect(meeting).not_to be_valid
expect(meeting.errors.count).to eq(1)
end
it 'is not invalid when setting date_time explicitly' do
meeting.start_time = DateTime.now
expect(meeting).to be_valid
end
it 'raises an error trying to set invalid time' do
expect { meeting.start_time = '-' }.to raise_error(Date::Error)
end
@m.start_date = Date.today.iso8601
expect(@m).to be_valid
it 'accepts changes after invalid dates' do
meeting.start_date = '-'
expect { meeting.start_time }.to raise_error(ArgumentError)
expect(meeting).not_to be_valid
@m.save!
expect(@m.start_time).to eq(Date.today + 10.hours)
meeting.start_date = Time.zone.today.iso8601
expect(meeting).to be_valid
meeting.save!
expect(meeting.start_time).to eq(Time.zone.today + 10.hours)
end
end
end
@ -170,8 +169,8 @@ describe Meeting, type: :model do
describe 'Timezones' do
shared_examples 'uses that zone' do |zone|
it do
@m.start_date = '2016-07-01'
expect(@m.start_time.zone).to eq(zone)
meeting.start_date = '2016-07-01'
expect(meeting.start_time.zone).to eq(zone)
end
end

@ -1054,7 +1054,7 @@ describe AccountController, type: :controller do
context 'with an expired token' do
before do
token.update_column :expires_on, Date.today - 1.day
token.update_column :expires_on, 1.day.ago
post :activate, params: activation_params
end

@ -90,8 +90,8 @@ FactoryBot.define do
factory :locked_user do
firstname { 'Locked' }
lastname { 'User' }
sequence(:login) { |n| "bob#{n}" }
sequence(:mail) { |n| "bob#{n}.bobbit@bob.com" }
sequence(:login) { |n| "locked#{n}" }
sequence(:mail) { |n| "locked#{n}@bob.com" }
password { 'adminADMIN!' }
password_confirmation { 'adminADMIN!' }
status { User.statuses[:locked] }

@ -56,7 +56,7 @@ describe Activities::Fetcher, 'integration', type: :model do
create(:wiki_page, wiki:, content:)
end
subject { instance.events(Date.today - 30, Date.today + 1) }
subject { instance.events(30.days.ago, 1.day.from_now) }
context 'activities globally' do
let!(:activities) { [work_package, message, news, time_entry, changeset, wiki_page.content] }

@ -29,8 +29,8 @@
require 'spec_helper'
describe Activities::WorkPackageActivityProvider, type: :model do
let(:event_scope) { 'work_packages' }
let(:work_package_edit_event) { 'work_package-edit' }
let(:event_scope) { 'work_packages' }
let(:work_package_edit_event) { 'work_package-edit' }
let(:work_package_closed_event) { 'work_package-closed' }
let(:user) { create :admin }
@ -47,7 +47,7 @@ describe Activities::WorkPackageActivityProvider, type: :model do
context 'when a work package has been created' do
let(:subject) do
Activities::WorkPackageActivityProvider
.find_events(event_scope, user, Date.yesterday, Date.tomorrow, {})
.find_events(event_scope, user, Time.zone.yesterday.to_datetime, Time.zone.tomorrow.to_datetime, {})
end
it 'has the edited event type' do
@ -66,7 +66,7 @@ describe Activities::WorkPackageActivityProvider, type: :model do
let(:subject) do
Activities::WorkPackageActivityProvider
.find_events(event_scope, user, Date.yesterday, Date.tomorrow, limit: 3)
.find_events(event_scope, user, Time.zone.yesterday.to_datetime, Time.zone.tomorrow.to_datetime, limit: 3)
.map { |a| a.journable_id.to_s }
end
@ -76,7 +76,7 @@ describe Activities::WorkPackageActivityProvider, type: :model do
context 'when a work package has been created and then closed' do
let(:subject) do
Activities::WorkPackageActivityProvider
.find_events(event_scope, user, Date.yesterday, Date.tomorrow, limit: 10)
.find_events(event_scope, user, Time.zone.yesterday.to_datetime, Time.zone.tomorrow.to_datetime, limit: 10)
end
before do
@ -138,7 +138,14 @@ describe Activities::WorkPackageActivityProvider, type: :model do
project.reload
Activities::WorkPackageActivityProvider
.find_events(event_scope, user, Date.yesterday, Date.tomorrow, project:, with_subprojects: true)
.find_events(
event_scope,
user,
Time.zone.yesterday.to_datetime,
Time.zone.tomorrow.to_datetime,
project:,
with_subprojects: true
)
end
it 'returns only visible work packages' do

@ -412,7 +412,7 @@ describe Repository::Git, type: :model do
def find_events(user, options = {})
options[:scope] = ['changesets']
fetcher = Activities::Fetcher.new(user, options)
fetcher.events(Date.today - 30, Date.today + 1)
fetcher.events(30.days.ago, 1.day.from_now)
end
it 'activitieses' do

@ -309,7 +309,7 @@ describe Repository::Subversion, type: :model do
def find_events(user, options = {})
options[:scope] = ['changesets']
fetcher = Activities::Fetcher.new(user, options)
fetcher.events(Date.today - 30, Date.today + 1)
fetcher.events(30.days.ago, 1.day.from_now)
end
it 'finds events' do

@ -72,8 +72,8 @@ messages_004:
parent_id:
forum_id: 1
messages_005:
created_at: <%= 3.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 3.days.ago.to_date.to_fs(:db) %>
created_at: <%= 3.days.ago %>
updated_at: <%= 3.days.ago %>
subject: 'RE: post 2'
id: 5
replies_count: 0
@ -83,8 +83,8 @@ messages_005:
parent_id: 4
forum_id: 1
messages_006:
created_at: <%= 2.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 2.days.ago.to_date.to_fs(:db) %>
created_at: <%= 2.days.ago %>
updated_at: <%= 2.days.ago %>
subject: 'RE: post 2'
id: 6
replies_count: 0
@ -94,8 +94,8 @@ messages_006:
parent_id: 4
forum_id: 1
messages_007:
created_at: <%= 2.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 2.days.ago.to_date.to_fs(:db) %>
created_at: <%= 2.days.ago %>
updated_at: <%= 2.days.ago %>
subject: 'Message on a private project'
id: 7
replies_count: 0

@ -28,9 +28,9 @@
---
issues_001:
created_at: <%= 3.days.ago.to_date.to_fs(:db) %>
created_at: <%= 3.days.ago %>
project_id: 1
updated_at: <%= 1.day.ago.to_date.to_fs(:db) %>
updated_at: <%= 1.day.ago %>
priority_id: 4
subject: Can't print recipes
id: 1
@ -79,9 +79,9 @@ issues_003:
start_date: <%= 15.day.ago.to_date.to_fs(:db) %>
due_date: <%= 5.day.ago.to_date.to_fs(:db) %>
issues_004:
created_at: <%= 5.days.ago.to_date.to_fs(:db) %>
created_at: <%= 5.days.ago %>
project_id: 2
updated_at: <%= 2.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 2.days.ago %>
priority_id: 4
subject: Issue on project 2
id: 4
@ -93,9 +93,9 @@ issues_004:
author_id: 2
status_id: 1
issues_005:
created_at: <%= 5.days.ago.to_date.to_fs(:db) %>
created_at: <%= 5.days.ago %>
project_id: 3
updated_at: <%= 2.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 2.days.ago %>
priority_id: 4
subject: Subproject issue
id: 5
@ -107,9 +107,9 @@ issues_005:
author_id: 2
status_id: 1
issues_006:
created_at: <%= 1.minute.ago.to_date.to_fs(:db) %>
created_at: <%= 1.minute.ago %>
project_id: 5
updated_at: <%= 1.minute.ago.to_date.to_fs(:db) %>
updated_at: <%= 1.minute.ago %>
priority_id: 4
subject: Issue of a private subproject
id: 6
@ -123,9 +123,9 @@ issues_006:
start_date: <%= Date.today.to_fs(:db) %>
due_date: <%= 1.days.from_now.to_date.to_fs(:db) %>
issues_007:
created_at: <%= 10.days.ago.to_date.to_fs(:db) %>
created_at: <%= 10.days.ago %>
project_id: 1
updated_at: <%= 10.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 10.days.ago %>
priority_id: 5
subject: Issue due today
id: 7
@ -139,9 +139,9 @@ issues_007:
start_date: <%= 10.days.ago.to_fs(:db) %>
due_date: <%= Date.today.to_fs(:db) %>
issues_008:
created_at: <%= 10.days.ago.to_date.to_fs(:db) %>
created_at: <%= 10.days.ago %>
project_id: 1
updated_at: <%= 10.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 10.days.ago %>
priority_id: 5
subject: Closed issue
id: 8
@ -155,9 +155,9 @@ issues_008:
start_date:
due_date:
issues_009:
created_at: <%= 1.minute.ago.to_date.to_fs(:db) %>
created_at: <%= 1.minute.ago %>
project_id: 5
updated_at: <%= 1.minute.ago.to_date.to_fs(:db) %>
updated_at: <%= 1.minute.ago %>
priority_id: 5
subject: Blocked Issue
id: 9
@ -171,9 +171,9 @@ issues_009:
start_date: <%= Date.today.to_fs(:db) %>
due_date: <%= 1.days.from_now.to_date.to_fs(:db) %>
issues_010:
created_at: <%= 1.minute.ago.to_date.to_fs(:db) %>
created_at: <%= 1.minute.ago %>
project_id: 5
updated_at: <%= 1.minute.ago.to_date.to_fs(:db) %>
updated_at: <%= 1.minute.ago %>
priority_id: 5
subject: Issue Doing the Blocking
id: 10
@ -187,9 +187,9 @@ issues_010:
start_date: <%= Date.today.to_fs(:db) %>
due_date: <%= 1.days.from_now.to_date.to_fs(:db) %>
issues_011:
created_at: <%= 3.days.ago.to_date.to_fs(:db) %>
created_at: <%= 3.days.ago %>
project_id: 1
updated_at: <%= 1.day.ago.to_date.to_fs(:db) %>
updated_at: <%= 1.day.ago %>
priority_id: 5
subject: Closed issue on a closed version
id: 11
@ -203,9 +203,9 @@ issues_011:
start_date: <%= 1.day.ago.to_date.to_fs(:db) %>
due_date:
issues_012:
created_at: <%= 3.days.ago.to_date.to_fs(:db) %>
created_at: <%= 3.days.ago %>
project_id: 1
updated_at: <%= 1.day.ago.to_date.to_fs(:db) %>
updated_at: <%= 1.day.ago %>
priority_id: 5
subject: Closed issue on a locked version
id: 12
@ -219,9 +219,9 @@ issues_012:
start_date: <%= 1.day.ago.to_date.to_fs(:db) %>
due_date:
issues_013:
created_at: <%= 5.days.ago.to_date.to_fs(:db) %>
created_at: <%= 5.days.ago %>
project_id: 3
updated_at: <%= 2.days.ago.to_date.to_fs(:db) %>
updated_at: <%= 2.days.ago %>
priority_id: 4
subject: Subproject issue two
id: 13

Loading…
Cancel
Save