fertile_forest 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,843 @@
1
+ ##
2
+ #
3
+ # restructure methods for Table
4
+ #
5
+ #
6
+ module StewEucen
7
+ # Name space of Stew Eucen's Acts
8
+ module Acts
9
+ # Name space of Fertile Forest
10
+ module FertileForest
11
+ # Name space of class methods for Fertile Forest
12
+ module Table
13
+ # This module is for extending into derived class by ActiveRecord.<br>
14
+ # The caption contains "Instance Methods",
15
+ # but it means "Class Methods" of each derived class.
16
+ module Reconstructers
17
+ #
18
+ # Graft subtree nodes.
19
+ #
20
+ # @param aim_obj [Entity|Integer] Top node of subtree to graft.
21
+ # @param base_obj [Entity|Integer] Base node to calc wedged queue.
22
+ # @param kinship [Boolean|Integer] Graft position from base_obj.
23
+ # Integer: As child.
24
+ # Boolean: As sibling.
25
+ # @return [Boolean] true: Success.
26
+ # @return [Boolean] false: Failure.
27
+ #
28
+ def graft(aim_obj, base_obj, kinship = -1)
29
+ transaction do
30
+ nodes = ff_resolve_nodes([aim_obj, base_obj], true).values # refresh
31
+ aim_node = nodes[0]
32
+ base_node = nodes[1]
33
+
34
+ raise ActiveRecord::Rollback if aim_node.blank? || base_node.blank?
35
+
36
+ # pick up node for wedged node to scoot over. (can be null)
37
+ wedged_node = ff_get_wedged_node(base_node, kinship)
38
+
39
+ is_sibling = ff_is_bool(kinship)
40
+ depth_offset = base_node.ff_depth - aim_node.ff_depth + (is_sibling ? 0 : 1)
41
+
42
+ return true if ff_fit_to_graft(aim_node, wedged_node, depth_offset)
43
+
44
+ # return value
45
+ ff_scoots_over(aim_node, wedged_node, depth_offset)
46
+ end # transaction end
47
+ end
48
+
49
+ protected
50
+
51
+ def ff_get_wedged_node(base_node, kinship)
52
+ is_sibling = ff_is_bool(kinship)
53
+
54
+ if is_sibling
55
+ ff_get_wedged_node_as_sibling(base_node, kinship)
56
+ else
57
+ ff_get_wedged_node_as_child(base_node, kinship)
58
+ end
59
+ end
60
+
61
+ def ff_get_wedged_node_as_child(base_node, nth)
62
+ # pickup wedged node by order of children
63
+ if 0 <= nth
64
+ nth_child = ff_nth_child(base_node, nth)
65
+ return nth_child if nth_child.present?
66
+ end
67
+
68
+ ff_get_boundary_node(base_node) # can be null
69
+ end
70
+
71
+ def ff_get_wedged_node_as_sibling(base_node, after_sibling_node)
72
+ if after_sibling_node
73
+ ff_get_boundary_node(base_node)
74
+ else
75
+ base_node
76
+ end
77
+ end
78
+
79
+ def ff_fit_to_graft(graft_node, wedged_node, depth_offset)
80
+ shift_queue = graft_node.ff_queue
81
+ shift_grove = graft_node.ff_grove
82
+
83
+ if has_grove? && shift_grove.blank?
84
+ # TODO: set error
85
+ return false
86
+ end
87
+
88
+ # exists subquery, can not work @compare_depth (avert to use coalesce())
89
+ graft_query = ff_subtree_scope(graft_node, SUBTREE_WITH_TOP_NODE, false)
90
+
91
+ # count grafting subtree span of queue
92
+ shift_boundary_queue = ff_get_boundary_queue(graft_node)
93
+ max_queue = ff_get_last_queue(shift_grove, 0) \
94
+ if shift_boundary_queue.blank? || wedged_node.blank?
95
+
96
+ if shift_boundary_queue.blank?
97
+ shift_span = max_queue - shift_queue + 1
98
+ else
99
+ shift_span = shift_boundary_queue - shift_queue
100
+ end
101
+
102
+ if wedged_node.blank?
103
+ wedged_space = QUEUE_MAX_VALUE - max_queue
104
+ else
105
+ prev_queue = ff_get_previous_queue(wedged_node)
106
+ wedged_space = wedged_node.ff_queue - prev_queue - 1
107
+ end
108
+
109
+ # If fit to graft as it is, execute to graft.
110
+ if shift_span <= wedged_space
111
+ if wedged_node.blank?
112
+ queue_offset = max_queue - shift_queue + 1
113
+ else
114
+ queue_offset = prev_queue - shift_queue + 1
115
+ end
116
+
117
+ return false if queue_offset == 0 && depth_offset == 0
118
+
119
+ graft_query = graft_query
120
+
121
+ update_columns = []
122
+ update_columns << "ff_queue = ff_queue + #{queue_offset}" \
123
+ if queue_offset != 0
124
+
125
+ update_columns << "ff_depth = ff_depth + #{depth_offset}" \
126
+ if depth_offset != 0
127
+
128
+ return 0 < graft_query.update_all(update_columns.join(', '))
129
+ end
130
+
131
+ # try to fit to shift with evenizing.
132
+ node_count = graft_query.count
133
+
134
+ if node_count <= wedged_space
135
+ return false if node_count < 1
136
+
137
+ return false if wedged_node.blank? && depth_offset == 0
138
+
139
+ # SET fields
140
+ queue_interval = [QUEUE_DEFAULT_INTERVAL, wedged_space / node_count].min.to_i
141
+ start_queue = (wedged_node.blank? ? max_queue : prev_queue) + 1 - queue_interval
142
+
143
+ update_columns[:ff_queue] = "@forest_queue := @forest_queue + #{queue_interval}"
144
+ update_columns[:ff_depth] = "ff_depth + #{depth_offset}" \
145
+ if depth_offset != 0
146
+
147
+ # WHERE conditions
148
+ update_conditions = ff_create_usual_conditions_hash(shift_grove)
149
+ update_conditions['ff_queue >='] = shift_queue if shift_queue.present?
150
+ update_conditions['ff_queue <' ] = shift_boundary_queue if shift_boundary_queue.present?
151
+
152
+ # use raw query, because can not use ORDER BY in standard updateAll().
153
+ update_rows = ff_update_all_in_order(
154
+ update_columns,
155
+ update_conditions,
156
+ ff_usual_order_array(),
157
+ "SET @forest_queue = #{start_queue}"
158
+ )
159
+
160
+ return 0 < update_rows.to_i
161
+ end
162
+
163
+ # can not fit to shift
164
+ false
165
+ end
166
+
167
+ def ff_scoots_over(shift_node, wedged_node, depth_offset)
168
+ # find boundary node of shift node (can be null)
169
+ aim_boundary_node = ff_get_boundary_node(shift_node)
170
+
171
+ return false unless ff_can_graft_by_node(shift_node, aim_boundary_node, wedged_node)
172
+
173
+ aim_grove = shift_node.ff_grove
174
+ aim_queue = shift_node.ff_queue
175
+
176
+ max_queue = ff_get_last_queue(aim_grove, 0) \
177
+ if aim_boundary_node.blank? || wedged_node.blank?
178
+
179
+ if aim_boundary_node.blank?
180
+ aim_tail_queue = max_queue
181
+ else
182
+ aim_tail_queue = aim_boundary_node.ff_queue - 1
183
+ end
184
+
185
+ if wedged_node.blank?
186
+ wedged_tail_queue = max_queue
187
+ else
188
+ wedged_tail_queue = wedged_node.ff_queue - 1
189
+ end
190
+
191
+ # moving direction progress/retrogress
192
+ # when same queue, as retrogress. Therefore use "<=".
193
+ is_retrogression = wedged_tail_queue <= aim_queue
194
+
195
+ # moving distance
196
+ move_offset = is_retrogression \
197
+ ? wedged_tail_queue - aim_queue + 1
198
+ : wedged_tail_queue - aim_tail_queue
199
+
200
+ involved_offset = (aim_tail_queue - aim_queue + 1) * (is_retrogression ? 1 : -1)
201
+
202
+ queue_offset_case = is_retrogression \
203
+ ? ff_create_case_expression(
204
+ nil,
205
+ [["ff_queue < #{aim_queue}", involved_offset]],
206
+ move_offset
207
+ )
208
+ : ff_create_case_expression(
209
+ nil,
210
+ [["ff_queue <= #{aim_tail_queue}", move_offset]],
211
+ involved_offset
212
+ )
213
+
214
+ ffqq = arel_table[@_ff_queue]
215
+ ffdd = arel_table[@_ff_depth]
216
+ ffgg = arel_table[@_ff_grove]
217
+
218
+ # WHERE conditions
219
+ head_queue = is_retrogression ? wedged_tail_queue + 1 : aim_queue
220
+ tail_queue = is_retrogression ? aim_tail_queue : wedged_tail_queue
221
+ scoots_over_query = ff_usual_conditions_scope(aim_grove)
222
+ .where(ffqq.gteq(head_queue))
223
+ .where(ffqq.lteq(tail_queue))
224
+
225
+ # UPDATE SET columns
226
+ # To set depth must be firstly, because it include condition of queue.
227
+ # If to set queue firstly, depth condition is changed before set.
228
+ update_columns = []
229
+ if depth_offset != 0
230
+ depth_offset_case = ff_create_case_expression(
231
+ nil,
232
+ [[
233
+ [ffqq.gteq(aim_queue).to_sql, 'AND', ffqq.lteq(aim_tail_queue).to_sql].join(' '),
234
+ depth_offset
235
+ ]],
236
+ #[["#{aim_queue} <= ff_queue AND ff_queue <= #{aim_tail_queue}", depth_offset]],
237
+ 0
238
+ )
239
+ update_columns << "ff_depth = ff_depth + (#{depth_offset_case})"
240
+ #update_columns << "ff_depth = ff_depth + (#{depth_offset_case})"
241
+ end
242
+ update_columns << "ff_queue = ff_queue + (#{queue_offset_case})"
243
+
244
+ # return value
245
+ 0 < scoots_over_query.update_all(update_columns.join(', '))
246
+ end
247
+
248
+ def ff_can_graft_by_node(aim_node, aim_boundary_node, wedged_node)
249
+ # When no wedged node, it means that last queue.
250
+ # In the case, can shift always
251
+ # TODO: should think with boundary node is null
252
+ return true if wedged_node.blank?
253
+
254
+ # If grove is different, can not shift.
255
+ if has_grove? && aim_node.ff_grove != wedged_node.ff_grove
256
+ # TODO: set error restructure.defferentGroves'
257
+ return false
258
+ end
259
+
260
+ #
261
+ # If wedged queue between the shifting subtree, can not shift.
262
+ # head < wedged < boundary
263
+ #
264
+ # can be "head == wedged". It is OK for shifting.
265
+ # because it means "depth-shifting".
266
+ #
267
+ # 2015/04/30
268
+ # float type aborted to use, because float has arithmetic error.
269
+ #
270
+ wedged_queue = wedged_node.ff_queue
271
+
272
+ # can be "head == wedged". It is OK for shifting.
273
+ # because it means "depth-shifting".
274
+ return true if wedged_queue <= aim_node.ff_queue
275
+
276
+ # In this case, must use boundary queue.
277
+ if aim_boundary_node.blank?
278
+ # TODO: set error restructure.graftIntoOwn
279
+ return false
280
+ end
281
+
282
+ # It is safe.
283
+ return true if aim_boundary_node.ff_queue < wedged_queue
284
+
285
+ # TODO: set error restructure.graftIntoOwn'
286
+ return false
287
+ end
288
+
289
+ public
290
+
291
+ #
292
+ # Reorder sibling nodes.
293
+ #
294
+ # @param args [mixed] Sibling nodes to permute.
295
+ # @return [Boolean] true: Success.
296
+ # @return [Boolean] false: Failure.
297
+ #
298
+ def permute(*args)
299
+ node_args = args.flatten
300
+
301
+ transaction do
302
+ sibling_nodes = ff_resolve_nodes(node_args, true) # refresh
303
+
304
+ # if node is only one, nothing to do.
305
+ raise ActiveRecord::Rollback if sibling_nodes.length < 2
306
+
307
+ # Aer they siblings?
308
+ current_orderd_child_nodes = siblings?(sibling_nodes.values)
309
+
310
+ raise ActiveRecord::Rollback if current_orderd_child_nodes.blank?
311
+
312
+ # if they are siblings, yield to permute.
313
+
314
+ # create array new orderd nodes.
315
+ new_orderd_sibling_nodes = []
316
+ new_orderd_ids = sibling_nodes.keys
317
+
318
+ current_orderd_child_nodes.each_pair do |the_id, the_node|
319
+ if sibling_nodes.has_key?(the_id)
320
+ picked_id = new_orderd_ids.shift
321
+ new_orderd_sibling_nodes << current_orderd_child_nodes[picked_id]
322
+ else
323
+ new_orderd_sibling_nodes << node
324
+ end
325
+ end
326
+
327
+ # get sorted nodes of all siblings by queue.
328
+ # TODO: need or not need?
329
+ current_queue_orderd_nodes = ff_queue_sorted_nodes(current_orderd_child_nodes.values)
330
+
331
+ # calc each siblingNode information.
332
+
333
+ # get tail node
334
+ tail_node = current_queue_orderd_nodes.last
335
+ aim_grove = tail_node.ff_grove
336
+
337
+ # get total boundary queue (can be null)
338
+ siblings_boundary_queue = ff_get_boundary_queue(tail_node)
339
+ total_tail_queue = siblings_boundary_queue.blank? \
340
+ ? ff_get_max_queue(aim_grove, 0)
341
+ : siblings_boundary_queue - 1
342
+
343
+ # set by current order.
344
+ node_attr_infos = {}
345
+ current_queue_orderd_nodes.each do |node|
346
+ node_attr_infos[node.id] = {}
347
+ end
348
+
349
+ last_node_index = current_queue_orderd_nodes.length - 1
350
+
351
+ node_id_hash = {}
352
+ current_queue_orderd_nodes.each.with_index do |the_node, i|
353
+ is_last = i == last_node_index
354
+ the_id = the_node.id
355
+ node_attr_infos[the_id][:is_last] = is_last
356
+
357
+ the_tail_queue = (is_last \
358
+ ? total_tail_queue
359
+ : (current_queue_orderd_nodes[i + 1].ff_queue - 1)
360
+ )
361
+ node_attr_infos[the_id][:tail_queue] = the_tail_queue
362
+
363
+ # calc queue-width each sibling
364
+ node_attr_infos[the_id][:queue_width] = the_tail_queue - the_node.ff_queue + 1
365
+
366
+ # must use &$xxxx, because do not clone node instance.
367
+ node_id_hash[the_id] = the_node
368
+ end
369
+
370
+ # get shifted range of queues
371
+ range_queue_head = current_queue_orderd_nodes.first.ff_queue
372
+
373
+ # calc moving queue span for each node.
374
+ has_changed = false
375
+ reduce_queue = range_queue_head # default value of new queue.
376
+ new_orderd_sibling_nodes.each do |the_node|
377
+ update_id = the_node.id
378
+ off = reduce_queue - node_id_hash[update_id].ff_queue
379
+
380
+ node_attr_infos[update_id][:ff_offset] = off
381
+ has_changed = true if off != 0
382
+
383
+ reduce_queue += node_attr_infos[update_id][:queue_width]
384
+ end
385
+
386
+ # no move, no update.
387
+ return false unless has_changed
388
+
389
+ # create case for update by original order of queue.
390
+ when_hash = ['CASE']
391
+ current_queue_orderd_nodes.each do |node|
392
+ orign_id = node.id
393
+ aim_info = node_attr_infos[orign_id]
394
+
395
+ off = aim_info[:ff_offset]
396
+ if aim_info[:is_last]
397
+ when_hash << "ELSE #{off}"
398
+ else
399
+ when_hash << "WHEN ff_queue <= #{aim_info[:tail_queue]} THEN #{off}"
400
+ end
401
+ end
402
+ when_hash <<= 'END'
403
+
404
+ case_string = when_hash.join(' ')
405
+
406
+ # execute to update all
407
+ # lteq(total_tail_queue) is for max_queue
408
+ ffqq = arel_table[@_ff_queue]
409
+ res = ff_usual_conditions_scope(aim_grove)
410
+ .ff_usual_order_scope()
411
+ .where(ffqq.gteq(range_queue_head))
412
+ .where(ffqq.lteq(total_tail_queue))
413
+ .update_all("ff_queue = ff_queue + (#{case_string})")
414
+
415
+ res
416
+ end # transaction end
417
+ end
418
+
419
+ #
420
+ # Permute in siblings as "Move To".
421
+ # @param node_obj [Entity|Integer] Moved node by Entity|id.
422
+ # @param nth [Integer] Move rank in sibling. (-1:As last sibling)
423
+ # @return [Boolean] true: Success.
424
+ # @return [Boolean] false: Failure.
425
+ #
426
+ def move_to(node_obj, nth = -1)
427
+ ff_move_node(node_obj, nth, false) # false: move to
428
+ end
429
+
430
+ #
431
+ # Permute in siblings as "Move By".
432
+ # @param node_obj [Entity|Integer] Moved node by Entity|id.
433
+ # @param step [Integer] Moving offset.
434
+ # @return [Boolean] true: Success.
435
+ # @return [Boolean] false: Failure.
436
+ #
437
+ def move_by(node_obj, step)
438
+ ff_move_node(node_obj, step, true) # true: move by
439
+ end
440
+
441
+ protected
442
+
443
+ def ff_move_node(node_obj, move_number, as_move_by = false)
444
+ aim_node = ff_resolve_nodes(node_obj)
445
+ return false if aim_node.blank?
446
+
447
+ siblings_query = siblings_of(aim_node, [@_id])
448
+ return false if siblings_query.blank?
449
+ sibling_nodes = siblings_query.all
450
+
451
+ aim_id = aim_node.id
452
+
453
+ move_number = move_number.to_i
454
+
455
+ # get orderd rank from move_number
456
+ if as_move_by
457
+ return false if move_number == 0
458
+
459
+ nth = nil
460
+ sibling_nodes.each.with_index do |node, i|
461
+ if node.id == aim_id
462
+ nth = i
463
+ break
464
+ end
465
+ end
466
+
467
+ return false if nth.nil?
468
+
469
+ nth = [0, nth + move_number].max
470
+ nth = -1 if sibling_nodes.length <= nth
471
+ else
472
+ nth = move_number
473
+ end
474
+
475
+ nth = sibling_nodes.length - 1 if nth < 0
476
+
477
+ return false if sibling_nodes.length <= nth
478
+
479
+ return false if sibling_nodes[nth].id == aim_id
480
+
481
+ new_orderd_nodes = []
482
+ sibling_nodes.each do |node|
483
+ new_orderd_nodes << node if node.id != aim_id
484
+ end
485
+
486
+ new_orderd_nodes.insert(nth, aim_node)
487
+
488
+ permute(new_orderd_nodes)
489
+ end
490
+
491
+ public
492
+
493
+ #
494
+ # Remove the node and shift depth of descendant nodes.
495
+ # soft delete
496
+ # (1) soft delete
497
+ # (2) grove delete
498
+ # (3) normal delete
499
+ # @param node_obj [Entity|Integer] Node to remove.
500
+ # @return [Boolean] true: Success.
501
+ # @return [Boolean] false: Failure.
502
+ #
503
+ def remove(node_obj)
504
+ transaction do
505
+ remove_node = ff_resolve_nodes(node_obj, true) # refresh
506
+ raise ActiveRecord::Rollback if remove_node.blank? # nil as dubious
507
+
508
+ aim_queue = remove_node.ff_queue
509
+ aim_depth = remove_node.ff_depth
510
+ aim_grove = remove_node.ff_grove
511
+
512
+ # get range of descendants for shifting these depth
513
+ if aim_depth == ROOT_DEPTH
514
+ offset_depth = 1
515
+ else
516
+ parent_node = genitor(remove_node)
517
+ raise ActiveRecord::Rollback if parent_node.blank?
518
+
519
+ offset_depth = aim_depth - parent_node.ff_depth
520
+ end
521
+
522
+ # for soft delete
523
+ # can not use subquery for same table in UPDATE
524
+ # Mysql2::Error: You can't specify target table 'categories' for update in FROM clause:
525
+ remove_query = ff_subtree_scope(remove_node, true, false)
526
+
527
+ ffdd = arel_table[@_ff_depth]
528
+
529
+ update_columns = []
530
+ depth_value = ff_create_case_expression(
531
+ @_ff_queue,
532
+ [[aim_queue, 0]], # when then
533
+ "ff_depth - #{offset_depth}" # else value
534
+ )
535
+ update_columns << "ff_depth = (#{depth_value})"
536
+
537
+ if has_soft_delete? || enable_grove_delete?
538
+ if has_soft_delete?
539
+ delete_value = ff_create_case_expression(
540
+ @_ff_queue,
541
+ [[aim_queue, ff_options[:delete_value]]], # when then
542
+ @_ff_soft_delete # else value
543
+ )
544
+ update_columns << "#{@_ff_soft_delete} = (#{delete_value})"
545
+ else
546
+ delete_value = ff_create_case_expression(
547
+ @_ff_queue,
548
+ [[aim_queue, -1]], # when then
549
+ 1 # else value
550
+ )
551
+ update_columns << "#{@_ff_grove} = #{@_ff_grove} * (#{delete_value})"
552
+ end
553
+
554
+ res = remove_query.update_all(update_columns.join(', '))
555
+
556
+ # hard delete
557
+ else
558
+ update_res = remove_query.update_all(update_columns.join(', '))
559
+ res = delete_all(@_id => remove_node.id)
560
+ end
561
+
562
+ res
563
+ end # tansaction end
564
+ end
565
+
566
+ #
567
+ # Prune subtree nodes.
568
+ # @param base_obj [Entity|Integer] Top node to prune.
569
+ # @param with_top [Boolean] Include base node in return query.
570
+ # @return [Boolean] true: Success.
571
+ # @return [Boolean] false: Failure.
572
+ #
573
+ def prune(base_obj, with_top = false)
574
+ transaction do
575
+ aim_node = ff_resolve_nodes(base_obj, true) # refresh
576
+ raise ActiveRecord::Rollback if aim_node.blank? # nil as dubious
577
+
578
+ aim_queue = aim_node.ff_queue
579
+ aim_depth = aim_node.ff_depth
580
+ aim_grove = aim_node.ff_grove
581
+
582
+ # boundry queue (can be nil)
583
+ aim_boundary_queue = ff_get_boundary_queue(aim_node)
584
+
585
+ # for soft delete
586
+ # can not use subquery for same table in UPDATE
587
+ # Mysql2::Error: You can't specify target table 'categories' for update in FROM clause:
588
+ prune_query = ff_subtree_scope(aim_node, with_top, false)
589
+
590
+ # soft delete
591
+ if has_soft_delete?
592
+ delete_key = @_ff_soft_delete
593
+ res = prune_query.update_all("#{delete_key} = #{ff_options[:delete_value]}")
594
+ elsif enable_grove_delete?
595
+ grove_key = @_ff_grove
596
+ res = prune_query.update_all("#{grove_key} = #{grove_key} * -1")
597
+ else
598
+ res = prune_query.delete_all
599
+ end
600
+
601
+ res
602
+ end # tansaction end
603
+ end
604
+
605
+ #
606
+ # Extinguish (remove top node and the descendant nodes).
607
+ # @param base_obj [Entity|Integer] Top node to extinguish.
608
+ # @return [Boolean] true: Success.
609
+ # @return [Boolean] false: Failure.
610
+ #
611
+ def extinguish(base_obj)
612
+ prune(base_obj, SUBTREE_WITH_TOP_NODE)
613
+ end
614
+
615
+ #
616
+ # Pollard (remove the descendant nodes).
617
+ # @param base_obj [Entity|Integer] Top node to pollard.
618
+ # @return [Boolean] true: Success.
619
+ # @return [Boolean] false: Failure.
620
+ #
621
+ def pollard(base_obj)
622
+ prune(base_obj, SUBTREE_WITHOUT_TOP_NODE)
623
+ end
624
+
625
+ ######################################################################
626
+
627
+ #
628
+ # Normalize ff_queue fields in ordered grove.
629
+ # @param node_obj [Entity|Integer] Start node.
630
+ # @param boundary_node_obj [Entity|Integer] Boundary node.
631
+ # @return [Boolean] true: Success.
632
+ # @return [Boolean] false: Failure.
633
+ #
634
+ def normalize_queue(node_obj = nil, boundary_node_obj = nil)
635
+ transaction do
636
+ # nodes can be nil
637
+ aim_node = ff_resolve_nodes(node_obj)
638
+ aim_boundary_node = ff_resolve_nodes(boundary_node_obj)
639
+
640
+ aim_top_queue = aim_node .blank? ? nil : aim_node .ff_queue
641
+ aim_boundary_queue = aim_boundary_node.blank? ? nil : aim_boundary_node.ff_queue
642
+
643
+ res = ff_evenize(aim_node.ff_grove, aim_top_queue, aim_boundary_queue, 0) # 0: no appmend node
644
+
645
+ # return value
646
+ if res.present?
647
+ res[EVENIZE_AFFECTED_ROWS_KEY]
648
+ else
649
+ false
650
+ end
651
+ end
652
+ end
653
+
654
+ protected
655
+
656
+ def ff_evenize(
657
+ grove_id,
658
+ aim_queue,
659
+ aim_boundary_queue,
660
+ add_count,
661
+ rear_justified = false
662
+ )
663
+ return nil if has_grove? && grove_id.to_i <= 0
664
+
665
+ # can evenize?
666
+ # 1.0 <= (boundaryQueue - headQueue) / (updatedNodeCount + appendNodeCount)
667
+ can_evenize = ff_can_evenize(grove_id, aim_queue, aim_boundary_queue, add_count)
668
+ return nil if can_evenize.blank?
669
+
670
+ queue_interval = can_evenize[:queue_interval]
671
+ node_count = can_evenize[:node_count]
672
+
673
+ # execute to slide
674
+
675
+ # calc defaut value of new queues
676
+ # add_count can be 0
677
+ head_queue = aim_queue || 0
678
+ if rear_justified
679
+ start_queue = head_queue + queue_interval * (add_count - 1)
680
+ else
681
+ start_queue = head_queue - queue_interval
682
+ end
683
+
684
+ #
685
+ # excute update query
686
+ #
687
+
688
+ # exists subquery, can not work @compare_depth (avert to use coalesce())
689
+ # Use both query "SET @ffqq = xxx" and "COALESCE(@ffqq, xxx)",
690
+ # because connection is cut between SET and UPDATE
691
+ update_conditions = ff_create_usual_conditions_hash(grove_id)
692
+ update_conditions['ff_queue >='] = aim_queue if aim_queue.present?
693
+ update_conditions['ff_queue <' ] = aim_boundary_queue if aim_boundary_queue.present?
694
+
695
+ update_rows = ff_update_all_in_order(
696
+ {ff_queue: "@forest_queue := @forest_queue + #{queue_interval}"},
697
+ update_conditions,
698
+ ff_usual_order_array(),
699
+ "SET @forest_queue = #{start_queue}"
700
+ )
701
+
702
+ return nil if update_rows.to_i <= 0
703
+
704
+ # return value
705
+ {
706
+ SPROUT_VACANT_QUEUE_KEY => head_queue + (rear_justified ? 0 : queue_interval * node_count),
707
+ EVENIZE_AFFECTED_ROWS_KEY => update_rows,
708
+ }
709
+ end
710
+
711
+ def ff_can_evenize(grove_id, aim_queue, aim_boundary_queue, add_count)
712
+ # get count of node for UPDATE
713
+ ffqq = arel_table[@_ff_queue]
714
+ aim_query = ff_usual_conditions_scope(grove_id)
715
+
716
+ aim_query.where!(ffqq.gteq(aim_queue)) if aim_queue.present?
717
+ aim_query.where!(ffqq.lt(aim_boundary_queue)) if aim_boundary_queue.present?
718
+
719
+ evenizing_count = aim_query.count
720
+
721
+ head_queue = aim_queue || 0
722
+
723
+ # can nomalize?
724
+ divid_number = evenizing_count + add_count
725
+ return nil if divid_number < 1
726
+
727
+ # get boundary queue for calc
728
+ if aim_boundary_queue.present?
729
+ aim_queue_span = aim_boundary_queue - head_queue
730
+ else
731
+ max_queue = ff_get_last_queue(grove_id, 0) # 0 for nil
732
+ request_queue_span = QUEUE_DEFAULT_INTERVAL * (add_count + 1)
733
+
734
+ if QUEUE_MAX_VALUE - max_queue < request_queue_span
735
+ aim_queue_span = QUEUE_MAX_VALUE - head_queue + 1
736
+ else
737
+ aim_queue_span = max_queue - head_queue + request_queue_span
738
+ end
739
+ end
740
+
741
+ queue_interval = aim_queue_span / divid_number
742
+
743
+ return nil if queue_interval < 1
744
+
745
+ # return value
746
+ {
747
+ queue_interval: queue_interval,
748
+ node_count: evenizing_count,
749
+ }
750
+ end
751
+
752
+ public
753
+
754
+ #
755
+ # Normalize ff_depth fields in ordered grove.
756
+ #
757
+ # @param grove_id [Integer|nil] Grove ID to find.
758
+ # @return [Boolean] true: Success.
759
+ # @return [Boolean] false: Failure.
760
+ #
761
+ def normalize_depth(grove_id = nil)
762
+ transaction do
763
+ return false if has_grove? && grove_id.blank?
764
+
765
+ feature_key = 'ff_is_fault'
766
+
767
+ columns = [
768
+ "@compare_depth + 1 < (@compare_depth := ff_depth) AS #{feature_key}",
769
+ ]
770
+
771
+ ffqq = arel_table[@_ff_queue]
772
+ ffdd = arel_table[@_ff_depth]
773
+
774
+ # exists subquery, can not work @compare_depth (avert to use coalesce())
775
+ aim_query = ff_required_columns_scope(columns)
776
+ .ff_usual_order_scope()
777
+ .ff_usual_conditions_scope(grove_id)
778
+ .having(feature_key)
779
+ .limit(1000)
780
+
781
+ # TODO: subtreeLimitSize
782
+
783
+ ff_raw_query("SET @compare_depth = #{ROOT_DEPTH}")
784
+
785
+ depth_offset_values = []
786
+ aim_query.all.each do |node|
787
+ prev_node = ff_get_previous_node(node)
788
+
789
+ next if prev_node.blank?
790
+
791
+ offset = node.ff_depth - prev_node.ff_depth - 1;
792
+ top_queue = prev_node.ff_queue
793
+ top_depth = prev_node.ff_depth
794
+ top_grove = prev_node.ff_grove if has_grove?
795
+
796
+ new_node = prev_node.clone
797
+
798
+ depth_offets = [];
799
+ (1 .. offset).each do |o|
800
+ new_node.ff_depth = top_depth + o
801
+ depth_offets << ff_get_boundary_queue(new_node)
802
+ end
803
+
804
+ grove_condition = has_grove? ? "#{@_ff_grove} = #{top_grove} AND" : '';
805
+
806
+ depth_offets.each do |boundary_queue|
807
+ boundary_condition = boundary_queue.blank? \
808
+ ? ''
809
+ : "AND ff_queue < #{boundary_queue}"
810
+
811
+ depth_offset_values << "(CASE WHEN #{grove_condition} #{top_queue} < ff_queue #{boundary_condition} THEN 1 ELSE 0 END)"
812
+ end
813
+ end
814
+
815
+ return false if depth_offset_values.blank?
816
+
817
+ update_query = ff_usual_conditions_scope(grove_id)
818
+ update_rows = update_query.update_all(
819
+ "#{@_ff_depth} = #{@_ff_depth} - " + depth_offset_values.join('-')
820
+ )
821
+
822
+ # return value
823
+ 0 < update_rows.to_i
824
+ end
825
+ end
826
+
827
+ def ff_usual_order_array(is_descent = false)
828
+ direction = is_descent ? 'DESC' : 'ASC'
829
+ res = {}
830
+ res[:ff_soft_delete] = direction if has_soft_delete?
831
+ res[:ff_grove ] = direction if has_grove?
832
+ res[:ff_depth ] = direction if is_descent
833
+ res[:ff_queue ] = direction
834
+
835
+ res
836
+ end
837
+
838
+ protected :ff_usual_order_array
839
+ end
840
+ end
841
+ end
842
+ end
843
+ end